From 1d55709e1336e826aa514ecb84c8c98c52e500c7 Mon Sep 17 00:00:00 2001 From: Nathaniel Guse Date: Fri, 12 Jul 2013 12:56:00 -0500 Subject: [ticket/11667] includeasset should be abstract PHPBB3-11667 --- phpBB/includes/template/twig/node/includeasset.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB') diff --git a/phpBB/includes/template/twig/node/includeasset.php b/phpBB/includes/template/twig/node/includeasset.php index 5abff10e3f..38c0498492 100644 --- a/phpBB/includes/template/twig/node/includeasset.php +++ b/phpBB/includes/template/twig/node/includeasset.php @@ -7,7 +7,7 @@ * */ -class phpbb_template_twig_node_includeasset extends Twig_Node +abstract class phpbb_template_twig_node_includeasset extends Twig_Node { /** @var Twig_Environment */ protected $environment; -- cgit v1.2.1 From 41d8bfa974900c9befbde06cc08060eb8a552ec8 Mon Sep 17 00:00:00 2001 From: Nathaniel Guse Date: Fri, 12 Jul 2013 13:32:40 -0500 Subject: [ticket/11667] Make functions abstract in includeasset Also comment properly PHPBB3-11667 --- phpBB/includes/template/twig/node/includeasset.php | 15 +++++++++++++++ phpBB/includes/template/twig/node/includecss.php | 14 ++++++++++---- phpBB/includes/template/twig/node/includejs.php | 14 ++++++++++---- 3 files changed, 35 insertions(+), 8 deletions(-) (limited to 'phpBB') diff --git a/phpBB/includes/template/twig/node/includeasset.php b/phpBB/includes/template/twig/node/includeasset.php index 38c0498492..b12d379157 100644 --- a/phpBB/includes/template/twig/node/includeasset.php +++ b/phpBB/includes/template/twig/node/includeasset.php @@ -62,4 +62,19 @@ abstract class phpbb_template_twig_node_includeasset extends Twig_Node ->raw("\n');\n") ; } + + /** + * Get the definition name + * + * @return string (e.g. 'SCRIPTS') + */ + abstract public function get_definition_name(); + + /** + * Append the output code for the asset + * + * @param Twig_Compiler A Twig_Compiler instance + * @return null + */ + abstract protected function append_asset(Twig_Compiler $compiler); } diff --git a/phpBB/includes/template/twig/node/includecss.php b/phpBB/includes/template/twig/node/includecss.php index 01fda44aad..450edb3e1e 100644 --- a/phpBB/includes/template/twig/node/includecss.php +++ b/phpBB/includes/template/twig/node/includecss.php @@ -9,16 +9,22 @@ class phpbb_template_twig_node_includecss extends phpbb_template_twig_node_includeasset { + /** + * Get the definition name + * + * @return string (e.g. 'SCRIPTS') + */ public function get_definition_name() { return 'STYLESHEETS'; } /** - * Compiles the node to PHP. - * - * @param Twig_Compiler A Twig_Compiler instance - */ + * Append the output code for the asset + * + * @param Twig_Compiler A Twig_Compiler instance + * @return null + */ public function append_asset(Twig_Compiler $compiler) { $compiler diff --git a/phpBB/includes/template/twig/node/includejs.php b/phpBB/includes/template/twig/node/includejs.php index fdf2bea3ed..50ab448e0f 100644 --- a/phpBB/includes/template/twig/node/includejs.php +++ b/phpBB/includes/template/twig/node/includejs.php @@ -9,16 +9,22 @@ class phpbb_template_twig_node_includejs extends phpbb_template_twig_node_includeasset { + /** + * Get the definition name + * + * @return string (e.g. 'SCRIPTS') + */ public function get_definition_name() { return 'SCRIPTS'; } /** - * Compiles the node to PHP. - * - * @param Twig_Compiler A Twig_Compiler instance - */ + * Append the output code for the asset + * + * @param Twig_Compiler A Twig_Compiler instance + * @return null + */ protected function append_asset(Twig_Compiler $compiler) { $config = $this->environment->get_phpbb_config(); -- cgit v1.2.1 From 06caac044479c3ff41f48157f40e8cb00e3d5e84 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Mon, 10 Jun 2013 13:48:21 +0200 Subject: [ticket/11574] Try to load updated service.yml before the default one PHPBB3-11574 --- phpBB/includes/di/extension/core.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'phpBB') diff --git a/phpBB/includes/di/extension/core.php b/phpBB/includes/di/extension/core.php index 9c36ba2fc4..d0a3ebdf99 100644 --- a/phpBB/includes/di/extension/core.php +++ b/phpBB/includes/di/extension/core.php @@ -51,8 +51,16 @@ class phpbb_di_extension_core extends Extension */ public function load(array $config, ContainerBuilder $container) { - $loader = new YamlFileLoader($container, new FileLocator(phpbb_realpath($this->root_path . 'config'))); - $loader->load('services.yml'); + if (file_exists($this->root_path . 'install/update/new/config/services.yml')) + { + $loader = new YamlFileLoader($container, new FileLocator(phpbb_realpath($this->root_path . 'install/update/new/config'))); + $loader->load('services.yml'); + } + else if (file_exists($this->root_path . 'config/services.yml')) + { + $loader = new YamlFileLoader($container, new FileLocator(phpbb_realpath($this->root_path . 'config'))); + $loader->load('services.yml'); + } } /** -- cgit v1.2.1 From 8aca9635f57630b89615d888cc8b92f5ac9b327c Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Mon, 10 Jun 2013 13:50:15 +0200 Subject: [ticket/11574] Find language files in update/new before throwing an error PHPBB3-11574 --- phpBB/includes/user.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'phpBB') diff --git a/phpBB/includes/user.php b/phpBB/includes/user.php index 5530fe3f03..f823e85148 100644 --- a/phpBB/includes/user.php +++ b/phpBB/includes/user.php @@ -590,6 +590,18 @@ class phpbb_user extends phpbb_session $language_filename = $lang_path . $this->lang_name . '/' . $filename . '.' . $phpEx; } + if (!file_exists($language_filename)) + { + // File was not found, try to find it in update directory + $orig_language_filename = $language_filename; + $language_filename = str_replace('language/', 'install/update/new/language/', $language_filename); + if (!file_exists($language_filename)) + { + // Not found either, go back to the original file name + $language_filename = $orig_language_filename; + } + } + if (!file_exists($language_filename)) { global $config; -- cgit v1.2.1 From e12fd2fdda76f629060c517aab9812b8565cc696 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Mon, 10 Jun 2013 13:51:55 +0200 Subject: [ticket/11574] Require new files in database_update.php and add a class loader PHPBB3-11574 --- phpBB/install/database_update.php | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) (limited to 'phpBB') diff --git a/phpBB/install/database_update.php b/phpBB/install/database_update.php index b20ca1e4ea..3341c20058 100644 --- a/phpBB/install/database_update.php +++ b/phpBB/install/database_update.php @@ -41,6 +41,26 @@ if (!function_exists('phpbb_require_updated')) } } +if (!function_exists('phpbb_include_updated')) +{ + function phpbb_include_updated($path, $optional = false) + { + global $phpbb_root_path; + + $new_path = $phpbb_root_path . 'install/update/new/' . $path; + $old_path = $phpbb_root_path . $path; + + if (file_exists($new_path)) + { + include($new_path); + } + else if (!$optional || file_exists($old_path)) + { + include($old_path); + } + } +} + function phpbb_end_update($cache, $config) { $cache->purge(); @@ -82,19 +102,21 @@ $phpbb_adm_relative_path = (isset($phpbb_adm_relative_path)) ? $phpbb_adm_relati $phpbb_admin_path = (defined('PHPBB_ADMIN_PATH')) ? PHPBB_ADMIN_PATH : $phpbb_root_path . $phpbb_adm_relative_path; // Include files -require($phpbb_root_path . 'includes/class_loader.' . $phpEx); +phpbb_require_updated('includes/class_loader.' . $phpEx); -require($phpbb_root_path . 'includes/functions.' . $phpEx); -require($phpbb_root_path . 'includes/functions_content.' . $phpEx); -require($phpbb_root_path . 'includes/functions_container.' . $phpEx); +phpbb_require_updated('includes/functions.' . $phpEx); +phpbb_require_updated('includes/functions_content.' . $phpEx); +phpbb_require_updated('includes/functions_container.' . $phpEx); -require($phpbb_root_path . 'includes/constants.' . $phpEx); -require($phpbb_root_path . 'includes/utf/utf_tools.' . $phpEx); +phpbb_require_updated('includes/constants.' . $phpEx); +phpbb_require_updated('includes/utf/utf_tools.' . $phpEx); // Set PHP error handler to ours set_error_handler(defined('PHPBB_MSG_HANDLER') ? PHPBB_MSG_HANDLER : 'msg_handler'); // Setup class loader first +$phpbb_class_loader_new = new phpbb_class_loader('phpbb_', "{$phpbb_root_path}install/update/new/includes/", $phpEx); +$phpbb_class_loader_new->register(); $phpbb_class_loader = new phpbb_class_loader('phpbb_', "{$phpbb_root_path}includes/", $phpEx); $phpbb_class_loader->register(); -- cgit v1.2.1 From f11993c0382d6942a14b37686d2c00dac97a6748 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Mon, 10 Jun 2013 13:54:48 +0200 Subject: [ticket/11574] Require new files in install/index.php and add a class loader PHPBB3-11574 --- phpBB/install/index.php | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) (limited to 'phpBB') diff --git a/phpBB/install/index.php b/phpBB/install/index.php index f745f51974..90cd71d7f3 100644 --- a/phpBB/install/index.php +++ b/phpBB/install/index.php @@ -43,6 +43,23 @@ function phpbb_require_updated($path, $optional = false) } } +function phpbb_include_updated($path, $optional = false) +{ + global $phpbb_root_path; + + $new_path = $phpbb_root_path . 'install/update/new/' . $path; + $old_path = $phpbb_root_path . $path; + + if (file_exists($new_path)) + { + include($new_path); + } + else if (!$optional || file_exists($old_path)) + { + include($old_path); + } +} + phpbb_require_updated('includes/startup.' . $phpEx); // Try to override some limits - maybe it helps some... @@ -78,18 +95,20 @@ $phpbb_adm_relative_path = (isset($phpbb_adm_relative_path)) ? $phpbb_adm_relati $phpbb_admin_path = (defined('PHPBB_ADMIN_PATH')) ? PHPBB_ADMIN_PATH : $phpbb_root_path . $phpbb_adm_relative_path; // Include essential scripts -require($phpbb_root_path . 'includes/class_loader.' . $phpEx); +phpbb_require_updated('includes/class_loader.' . $phpEx); -require($phpbb_root_path . 'includes/functions.' . $phpEx); -require($phpbb_root_path . 'includes/functions_container.' . $phpEx); +phpbb_require_updated('includes/functions.' . $phpEx); +phpbb_require_updated('includes/functions_container.' . $phpEx); phpbb_require_updated('includes/functions_content.' . $phpEx, true); -include($phpbb_root_path . 'includes/functions_admin.' . $phpEx); -include($phpbb_root_path . 'includes/utf/utf_tools.' . $phpEx); -require($phpbb_root_path . 'includes/functions_install.' . $phpEx); +phpbb_include_updated('includes/functions_admin.' . $phpEx); +phpbb_include_updated('includes/utf/utf_tools.' . $phpEx); +phpbb_require_updated('includes/functions_install.' . $phpEx); // Setup class loader first +$phpbb_class_loader_new = new phpbb_class_loader('phpbb_', "{$phpbb_root_path}install/update/new/includes/", $phpEx); +$phpbb_class_loader_new->register(); $phpbb_class_loader = new phpbb_class_loader('phpbb_', "{$phpbb_root_path}includes/", $phpEx); $phpbb_class_loader->register(); $phpbb_class_loader_ext = new phpbb_class_loader('phpbb_ext_', "{$phpbb_root_path}ext/", $phpEx); -- cgit v1.2.1 From deac5f53e3e4131c315c93ce1324899da08d233e Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Mon, 10 Jun 2013 13:56:32 +0200 Subject: [ticket/11574] Load new language files whenever possible PHPBB3-11574 --- phpBB/install/index.php | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) (limited to 'phpBB') diff --git a/phpBB/install/index.php b/phpBB/install/index.php index 90cd71d7f3..5494b57610 100644 --- a/phpBB/install/index.php +++ b/phpBB/install/index.php @@ -186,11 +186,23 @@ if (!file_exists($phpbb_root_path . 'language/' . $language) || !is_dir($phpbb_r } // And finally, load the relevant language files -include($phpbb_root_path . 'language/' . $language . '/common.' . $phpEx); -include($phpbb_root_path . 'language/' . $language . '/acp/common.' . $phpEx); -include($phpbb_root_path . 'language/' . $language . '/acp/board.' . $phpEx); -include($phpbb_root_path . 'language/' . $language . '/install.' . $phpEx); -include($phpbb_root_path . 'language/' . $language . '/posting.' . $phpEx); +$load_lang_files = array('common', 'acp/common', 'acp/board', 'install', 'posting'); +$new_path = $phpbb_root_path . 'install/update/new/language/' . $language . '/'; +$old_path = $phpbb_root_path . 'language/' . $language . '/'; + +// NOTE: we can not use "phpbb_include_updated" as the files uses vars which would be required +// to be global while loading. +foreach ($load_lang_files as $lang_file) +{ + if (file_exists($new_path . $lang_file . '.' . $phpEx)) + { + include($new_path . $lang_file . '.' . $phpEx); + } + else + { + include($old_path . $lang_file . '.' . $phpEx); + } +} // usually we would need every single constant here - and it would be consistent. For 3.0.x, use a dirty hack... :( -- cgit v1.2.1 From 3eaeede32176d8831f12529aa2053123293b67ca Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Mon, 10 Jun 2013 13:57:47 +0200 Subject: [ticket/11574] Use request object rather then request_var function PHPBB3-11574 --- phpBB/install/index.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'phpBB') diff --git a/phpBB/install/index.php b/phpBB/install/index.php index 5494b57610..fd6734cbfb 100644 --- a/phpBB/install/index.php +++ b/phpBB/install/index.php @@ -127,7 +127,7 @@ $request = $phpbb_container->get('request'); request_var('', 0, false, false, $request); // "dependency injection" for a function // Try and load an appropriate language if required -$language = basename(request_var('language', '')); +$language = basename($request->variable('language', '')); if ($request->header('Accept-Language') && !$language) { @@ -212,8 +212,8 @@ define('CHMOD_READ', 4); define('CHMOD_WRITE', 2); define('CHMOD_EXECUTE', 1); -$mode = request_var('mode', 'overview'); -$sub = request_var('sub', ''); +$mode = $request->variable('mode', 'overview'); +$sub = $request->variable('sub', ''); // Set PHP error handler to ours set_error_handler(defined('PHPBB_MSG_HANDLER') ? PHPBB_MSG_HANDLER : 'msg_handler'); -- cgit v1.2.1 From fcf343733887d7d42a3060c48b1a009ae0520467 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Mon, 10 Jun 2013 13:58:36 +0200 Subject: [ticket/11574] Add correct language parameter to return links PHPBB3-11574 --- phpBB/install/database_update.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'phpBB') diff --git a/phpBB/install/database_update.php b/phpBB/install/database_update.php index 3341c20058..de7f4f202e 100644 --- a/phpBB/install/database_update.php +++ b/phpBB/install/database_update.php @@ -286,7 +286,7 @@ while (!$migrator->finished()) if ((time() - $update_start_time) >= $safe_time_limit) { echo $user->lang['DATABASE_UPDATE_NOT_COMPLETED'] . '
'; - echo '' . $user->lang['DATABASE_UPDATE_CONTINUE'] . ''; + echo '' . $user->lang['DATABASE_UPDATE_CONTINUE'] . ''; phpbb_end_update($cache, $config); } @@ -302,7 +302,7 @@ echo $user->lang['DATABASE_UPDATE_COMPLETE'] . '
'; if ($request->variable('type', 0)) { echo $user->lang['INLINE_UPDATE_SUCCESSFUL'] . '

'; - echo '' . $user->lang['CONTINUE_UPDATE_NOW'] . ''; + echo '' . $user->lang['CONTINUE_UPDATE_NOW'] . ''; } else { -- cgit v1.2.1 From 14ff1ef540f71e765ceeaa53b87ad62e4c8d8a40 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Mon, 10 Jun 2013 14:44:49 +0200 Subject: [ticket/11574] Create phpbb_log object before using it. PHPBB3-11574 --- phpBB/install/install_update.php | 3 +++ 1 file changed, 3 insertions(+) (limited to 'phpBB') diff --git a/phpBB/install/install_update.php b/phpBB/install/install_update.php index df9b6c1c7e..90c56bcdcc 100644 --- a/phpBB/install/install_update.php +++ b/phpBB/install/install_update.php @@ -509,6 +509,9 @@ class install_update extends module if ($all_up_to_date) { + global $phpbb_log, $phpbb_container; + $phpbb_log = $phpbb_container->get('log'); + // Add database update to log add_log('admin', 'LOG_UPDATE_PHPBB', $this->current_version, $this->update_to_version); -- cgit v1.2.1 From fe7823b6685975012d3c033b335d7ed5fa9756d7 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Sat, 6 Jul 2013 19:26:37 +0200 Subject: [ticket/11574] Use log object instead of old function PHPBB3-11574 --- phpBB/install/install_update.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'phpBB') diff --git a/phpBB/install/install_update.php b/phpBB/install/install_update.php index 90c56bcdcc..38d9f66629 100644 --- a/phpBB/install/install_update.php +++ b/phpBB/install/install_update.php @@ -509,11 +509,11 @@ class install_update extends module if ($all_up_to_date) { - global $phpbb_log, $phpbb_container; + global $phpbb_container; $phpbb_log = $phpbb_container->get('log'); // Add database update to log - add_log('admin', 'LOG_UPDATE_PHPBB', $this->current_version, $this->update_to_version); + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_UPDATE_PHPBB', time(), array($this->current_version, $this->update_to_version)); $db->sql_return_on_error(true); $db->sql_query('DELETE FROM ' . CONFIG_TABLE . " WHERE config_name = 'version_update_from'"); -- cgit v1.2.1 From 3bccd10ccd509ae622cfd23f075fce1d1289888a Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Sat, 6 Jul 2013 19:35:42 +0200 Subject: [ticket/11574] Only fall back to install/update versions, when IN_INSTALL ;) PHPBB3-11574 --- phpBB/includes/di/extension/core.php | 3 ++- phpBB/includes/user.php | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) (limited to 'phpBB') diff --git a/phpBB/includes/di/extension/core.php b/phpBB/includes/di/extension/core.php index d0a3ebdf99..4e1159e6fd 100644 --- a/phpBB/includes/di/extension/core.php +++ b/phpBB/includes/di/extension/core.php @@ -51,7 +51,8 @@ class phpbb_di_extension_core extends Extension */ public function load(array $config, ContainerBuilder $container) { - if (file_exists($this->root_path . 'install/update/new/config/services.yml')) + // If we are in install, try to use the updated version, when available + if (defined('IN_INSTALL') && file_exists($this->root_path . 'install/update/new/config/services.yml')) { $loader = new YamlFileLoader($container, new FileLocator(phpbb_realpath($this->root_path . 'install/update/new/config'))); $loader->load('services.yml'); diff --git a/phpBB/includes/user.php b/phpBB/includes/user.php index f823e85148..f8ff3b8b13 100644 --- a/phpBB/includes/user.php +++ b/phpBB/includes/user.php @@ -590,14 +590,15 @@ class phpbb_user extends phpbb_session $language_filename = $lang_path . $this->lang_name . '/' . $filename . '.' . $phpEx; } - if (!file_exists($language_filename)) + if (defined('IN_INSTALL')) { - // File was not found, try to find it in update directory + // If we are in install, try to use the updated version, when available $orig_language_filename = $language_filename; $language_filename = str_replace('language/', 'install/update/new/language/', $language_filename); + if (!file_exists($language_filename)) { - // Not found either, go back to the original file name + // Not found, go back to the original file name $language_filename = $orig_language_filename; } } -- cgit v1.2.1 From 6c52fae750ed2955b8c0d737e72f101f4d6f2e3a Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Thu, 11 Jul 2013 15:55:04 +0200 Subject: [ticket/11574] Include normalizer so it loads form the correct directory PHPBB3-11574 --- phpBB/install/index.php | 1 + 1 file changed, 1 insertion(+) (limited to 'phpBB') diff --git a/phpBB/install/index.php b/phpBB/install/index.php index fd6734cbfb..4051a5a08b 100644 --- a/phpBB/install/index.php +++ b/phpBB/install/index.php @@ -103,6 +103,7 @@ phpbb_require_updated('includes/functions_container.' . $phpEx); phpbb_require_updated('includes/functions_content.' . $phpEx, true); phpbb_include_updated('includes/functions_admin.' . $phpEx); +phpbb_include_updated('includes/utf/utf_normalizer.' . $phpEx); phpbb_include_updated('includes/utf/utf_tools.' . $phpEx); phpbb_require_updated('includes/functions_install.' . $phpEx); -- cgit v1.2.1 From 1dea0286a48b4bac6702aad673e13e281690adfb Mon Sep 17 00:00:00 2001 From: Igor Wiedler Date: Fri, 12 Jul 2013 15:40:49 -0400 Subject: [ticket/11574] Use alternate DI config file for updater PHPBB3-11574 --- phpBB/includes/di/extension/core.php | 23 +++++++---------------- phpBB/includes/functions_container.php | 7 +++++-- phpBB/install/database_update.php | 5 ++++- 3 files changed, 16 insertions(+), 19 deletions(-) (limited to 'phpBB') diff --git a/phpBB/includes/di/extension/core.php b/phpBB/includes/di/extension/core.php index 4e1159e6fd..9d59a24b7e 100644 --- a/phpBB/includes/di/extension/core.php +++ b/phpBB/includes/di/extension/core.php @@ -26,19 +26,19 @@ use Symfony\Component\Config\FileLocator; class phpbb_di_extension_core extends Extension { /** - * phpBB Root path + * Config path * @var string */ - protected $root_path; + protected $config_path; /** * Constructor * - * @param string $root_path Root path + * @param string $config_path Config path */ - public function __construct($root_path) + public function __construct($config_path) { - $this->root_path = $root_path; + $this->config_path = $config_path; } /** @@ -51,17 +51,8 @@ class phpbb_di_extension_core extends Extension */ public function load(array $config, ContainerBuilder $container) { - // If we are in install, try to use the updated version, when available - if (defined('IN_INSTALL') && file_exists($this->root_path . 'install/update/new/config/services.yml')) - { - $loader = new YamlFileLoader($container, new FileLocator(phpbb_realpath($this->root_path . 'install/update/new/config'))); - $loader->load('services.yml'); - } - else if (file_exists($this->root_path . 'config/services.yml')) - { - $loader = new YamlFileLoader($container, new FileLocator(phpbb_realpath($this->root_path . 'config'))); - $loader->load('services.yml'); - } + $loader = new YamlFileLoader($container, new FileLocator(phpbb_realpath($this->config_path))); + $loader->load('services.yml'); } /** diff --git a/phpBB/includes/functions_container.php b/phpBB/includes/functions_container.php index 106b7d75cc..d302b75350 100644 --- a/phpBB/includes/functions_container.php +++ b/phpBB/includes/functions_container.php @@ -53,7 +53,10 @@ function phpbb_create_container(array $extensions, $phpbb_root_path, $php_ext) */ function phpbb_create_install_container($phpbb_root_path, $php_ext) { - $core = new phpbb_di_extension_core($phpbb_root_path); + $other_config_path = $phpbb_root_path . 'install/update/new/config'; + $config_path = file_exists($other_config_path . 'services.yml') ? $other_config_path : $phpbb_root_path . 'config'; + + $core = new phpbb_di_extension_core($config_path); $container = phpbb_create_container(array($core), $phpbb_root_path, $php_ext); $container->setParameter('core.root_path', $phpbb_root_path); @@ -175,7 +178,7 @@ function phpbb_create_default_container($phpbb_root_path, $php_ext) return phpbb_create_dumped_container_unless_debug( array( new phpbb_di_extension_config($phpbb_root_path . 'config.' . $php_ext), - new phpbb_di_extension_core($phpbb_root_path), + new phpbb_di_extension_core($phpbb_root_path . 'config'), ), array( new phpbb_di_pass_collection_pass(), diff --git a/phpBB/install/database_update.php b/phpBB/install/database_update.php index de7f4f202e..b0e28958ac 100644 --- a/phpBB/install/database_update.php +++ b/phpBB/install/database_update.php @@ -121,9 +121,12 @@ $phpbb_class_loader = new phpbb_class_loader('phpbb_', "{$phpbb_root_path}includ $phpbb_class_loader->register(); // Set up container (must be done here because extensions table may not exist) +$other_config_path = $phpbb_root_path . 'install/update/new/config'; +$config_path = file_exists($other_config_path . 'services.yml') ? $other_config_path : $phpbb_root_path; + $container_extensions = array( new phpbb_di_extension_config($phpbb_root_path . 'config.' . $phpEx), - new phpbb_di_extension_core($phpbb_root_path), + new phpbb_di_extension_core($config_path), ); $container_passes = array( new phpbb_di_pass_collection_pass(), -- cgit v1.2.1 From 51ab2c710e91d98bb182dded1e9d5462451151e8 Mon Sep 17 00:00:00 2001 From: Igor Wiedler Date: Fri, 12 Jul 2013 16:40:20 -0400 Subject: [ticket/11574] Make install language filename less crazy PHPBB3-11574 --- phpBB/includes/user.php | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) (limited to 'phpBB') diff --git a/phpBB/includes/user.php b/phpBB/includes/user.php index f8ff3b8b13..b39438e315 100644 --- a/phpBB/includes/user.php +++ b/phpBB/includes/user.php @@ -590,17 +590,11 @@ class phpbb_user extends phpbb_session $language_filename = $lang_path . $this->lang_name . '/' . $filename . '.' . $phpEx; } - if (defined('IN_INSTALL')) + // If we are in install, try to use the updated version, when available + $install_language_filename = str_replace('language/', 'install/update/new/language/', $language_filename); + if (defined('IN_INSTALL') && file_exists($install_language_filename)) { - // If we are in install, try to use the updated version, when available - $orig_language_filename = $language_filename; - $language_filename = str_replace('language/', 'install/update/new/language/', $language_filename); - - if (!file_exists($language_filename)) - { - // Not found, go back to the original file name - $language_filename = $orig_language_filename; - } + $language_filename = $install_language_filename; } if (!file_exists($language_filename)) -- cgit v1.2.1 From 31fed4215067ee39e7396f010a06093fe66352ee Mon Sep 17 00:00:00 2001 From: Bruno Ais Date: Sat, 13 Jul 2013 12:58:04 +0100 Subject: [ticket/11656] generate_text_for_display on memberlist.php sub-task of ticket PHPBB3-11635: find and fix all bypasses of generate_text_for_* PHPBB3-11656 --- phpBB/memberlist.php | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) (limited to 'phpBB') diff --git a/phpBB/memberlist.php b/phpBB/memberlist.php index 7ecf332720..6156e6a292 100644 --- a/phpBB/memberlist.php +++ b/phpBB/memberlist.php @@ -561,17 +561,8 @@ switch ($mode) if ($member['user_sig']) { - $member['user_sig'] = censor_text($member['user_sig']); - - if ($member['user_sig_bbcode_bitfield']) - { - include_once($phpbb_root_path . 'includes/bbcode.' . $phpEx); - $bbcode = new bbcode(); - $bbcode->bbcode_second_pass($member['user_sig'], $member['user_sig_bbcode_uid'], $member['user_sig_bbcode_bitfield']); - } - - $member['user_sig'] = bbcode_nl2br($member['user_sig']); - $member['user_sig'] = smiley_text($member['user_sig']); + $member['user_sig'] = generate_text_for_display($member['user_sig'], $member['user_sig_bbcode_uid'], + $member['user_sig_bbcode_bitfield'], OPTION_FLAG_BBCODE || OPTION_FLAG_SMILIES, true); } $poster_avatar = phpbb_get_user_avatar($member); -- cgit v1.2.1 From fca4bc53232e22233711a275e252a8006dd89e9a Mon Sep 17 00:00:00 2001 From: Bruno Ais Date: Sat, 13 Jul 2013 15:55:37 +0100 Subject: [ticket/11656] Remove line break in function call sub-task of ticket PHPBB3-11635: find and fix all bypasses of generate_text_for_* PHPBB3-11656 --- phpBB/memberlist.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'phpBB') diff --git a/phpBB/memberlist.php b/phpBB/memberlist.php index 6156e6a292..09b9dab5c1 100644 --- a/phpBB/memberlist.php +++ b/phpBB/memberlist.php @@ -561,8 +561,7 @@ switch ($mode) if ($member['user_sig']) { - $member['user_sig'] = generate_text_for_display($member['user_sig'], $member['user_sig_bbcode_uid'], - $member['user_sig_bbcode_bitfield'], OPTION_FLAG_BBCODE || OPTION_FLAG_SMILIES, true); + $member['user_sig'] = generate_text_for_display($member['user_sig'], $member['user_sig_bbcode_uid'], $member['user_sig_bbcode_bitfield'], OPTION_FLAG_BBCODE || OPTION_FLAG_SMILIES, true); } $poster_avatar = phpbb_get_user_avatar($member); -- cgit v1.2.1 From 402d987ccbeacb494f1147cdee047a6cf1f19f7b Mon Sep 17 00:00:00 2001 From: Bruno Ais Date: Sat, 13 Jul 2013 15:56:55 +0100 Subject: [ticket/11656] Wrong bitwise OR sub-task of ticket PHPBB3-11635: find and fix all bypasses of generate_text_for_* PHPBB3-11656 --- phpBB/memberlist.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB') diff --git a/phpBB/memberlist.php b/phpBB/memberlist.php index 09b9dab5c1..f8ee82084c 100644 --- a/phpBB/memberlist.php +++ b/phpBB/memberlist.php @@ -561,7 +561,7 @@ switch ($mode) if ($member['user_sig']) { - $member['user_sig'] = generate_text_for_display($member['user_sig'], $member['user_sig_bbcode_uid'], $member['user_sig_bbcode_bitfield'], OPTION_FLAG_BBCODE || OPTION_FLAG_SMILIES, true); + $member['user_sig'] = generate_text_for_display($member['user_sig'], $member['user_sig_bbcode_uid'], $member['user_sig_bbcode_bitfield'], OPTION_FLAG_BBCODE | OPTION_FLAG_SMILIES, true); } $poster_avatar = phpbb_get_user_avatar($member); -- cgit v1.2.1 From b4fcdc51e9df126faf5e9aabcbaa50bb33da0bd0 Mon Sep 17 00:00:00 2001 From: Bruno Ais Date: Sat, 13 Jul 2013 13:49:19 +0100 Subject: [ticket/11638] generate_text_for_display on viewtopic.php lines: 835-843 sub-task of ticket PHPBB3-11635: find and fix all bypasses of generate_text_for_* PHPBB3-11638 --- phpBB/viewtopic.php | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) (limited to 'phpBB') diff --git a/phpBB/viewtopic.php b/phpBB/viewtopic.php index 4dd03202f1..6b789bbb99 100644 --- a/phpBB/viewtopic.php +++ b/phpBB/viewtopic.php @@ -828,26 +828,15 @@ if (!empty($topic_data['poll_start'])) $poll_total += $poll_option['poll_option_total']; } - if ($poll_info[0]['bbcode_bitfield']) - { - $poll_bbcode = new bbcode(); - } - else - { - $poll_bbcode = false; + $parse_bbcode_flags = OPTION_FLAG_SMILIES; + + if(empty($poll_info[0]['bbcode_bitfield'])){ + $parse_bbcode_flags |= OPTION_FLAG_BBCODE; } for ($i = 0, $size = sizeof($poll_info); $i < $size; $i++) { - $poll_info[$i]['poll_option_text'] = censor_text($poll_info[$i]['poll_option_text']); - - if ($poll_bbcode !== false) - { - $poll_bbcode->bbcode_second_pass($poll_info[$i]['poll_option_text'], $poll_info[$i]['bbcode_uid'], $poll_option['bbcode_bitfield']); - } - - $poll_info[$i]['poll_option_text'] = bbcode_nl2br($poll_info[$i]['poll_option_text']); - $poll_info[$i]['poll_option_text'] = smiley_text($poll_info[$i]['poll_option_text']); + $poll_info[$i]['poll_option_text'] = generate_text_for_display($poll_info[$i]['poll_option_text'], $poll_info[$i]['bbcode_uid'], $poll_option['bbcode_bitfield'], $parse_bbcode_flags, true); } $topic_data['poll_title'] = censor_text($topic_data['poll_title']); -- cgit v1.2.1 From 97054d81f82123f17cd3680326932a8fd021ffa2 Mon Sep 17 00:00:00 2001 From: Bruno Ais Date: Sat, 13 Jul 2013 13:50:44 +0100 Subject: [ticket/11638] generate_text_for_display on viewtopic.php lines: 846-854 sub-task of ticket PHPBB3-11635: find and fix all bypasses of generate_text_for_* PHPBB3-11638 --- phpBB/viewtopic.php | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) (limited to 'phpBB') diff --git a/phpBB/viewtopic.php b/phpBB/viewtopic.php index 6b789bbb99..d295b91f43 100644 --- a/phpBB/viewtopic.php +++ b/phpBB/viewtopic.php @@ -839,17 +839,9 @@ if (!empty($topic_data['poll_start'])) $poll_info[$i]['poll_option_text'] = generate_text_for_display($poll_info[$i]['poll_option_text'], $poll_info[$i]['bbcode_uid'], $poll_option['bbcode_bitfield'], $parse_bbcode_flags, true); } - $topic_data['poll_title'] = censor_text($topic_data['poll_title']); - - if ($poll_bbcode !== false) - { - $poll_bbcode->bbcode_second_pass($topic_data['poll_title'], $poll_info[0]['bbcode_uid'], $poll_info[0]['bbcode_bitfield']); - } - - $topic_data['poll_title'] = bbcode_nl2br($topic_data['poll_title']); - $topic_data['poll_title'] = smiley_text($topic_data['poll_title']); - - unset($poll_bbcode); + $topic_data['poll_title'] = generate_text_for_display($topic_data['poll_title'], $poll_info[0]['bbcode_uid'], $poll_info[0]['bbcode_bitfield'], $parse_bbcode_flags, true); + + unset($parse_bbcode_flags); foreach ($poll_info as $poll_option) { -- cgit v1.2.1 From a58ccdabf30cad5b0373c266fc69368c65cee1a1 Mon Sep 17 00:00:00 2001 From: Bruno Ais Date: Sat, 13 Jul 2013 14:35:22 +0100 Subject: [ticket/11638] generate_text_for_display on viewtopic.php lines: 1395-1403 sub-task of ticket PHPBB3-11635: find and fix all bypasses of generate_text_for_* PHPBB3-11638 --- phpBB/viewtopic.php | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) (limited to 'phpBB') diff --git a/phpBB/viewtopic.php b/phpBB/viewtopic.php index d295b91f43..34586b1491 100644 --- a/phpBB/viewtopic.php +++ b/phpBB/viewtopic.php @@ -1380,16 +1380,8 @@ for ($i = 0, $end = sizeof($post_list); $i < $end; ++$i) // End signature parsing, only if needed if ($user_cache[$poster_id]['sig'] && $row['enable_sig'] && empty($user_cache[$poster_id]['sig_parsed'])) { - $user_cache[$poster_id]['sig'] = censor_text($user_cache[$poster_id]['sig']); - - if ($user_cache[$poster_id]['sig_bbcode_bitfield']) - { - $bbcode->bbcode_second_pass($user_cache[$poster_id]['sig'], $user_cache[$poster_id]['sig_bbcode_uid'], $user_cache[$poster_id]['sig_bbcode_bitfield']); - } - - $user_cache[$poster_id]['sig'] = bbcode_nl2br($user_cache[$poster_id]['sig']); - $user_cache[$poster_id]['sig'] = smiley_text($user_cache[$poster_id]['sig']); - $user_cache[$poster_id]['sig_parsed'] = true; + $include_bbcode_parse = $user_cache[$poster_id]['sig_bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0; + $user_cache[$poster_id]['sig'] = generate_text_for_display($user_cache[$poster_id]['sig'], $user_cache[$poster_id]['sig_bbcode_uid'], $user_cache[$poster_id]['sig_bbcode_bitfield'], $include_bbcode_parse | OPTION_FLAG_SMILIES, true); } // Parse the message and subject -- cgit v1.2.1 From 713a2ba573ab49d2add2522392b1f91ca3065dea Mon Sep 17 00:00:00 2001 From: Bruno Ais Date: Sat, 13 Jul 2013 14:35:50 +0100 Subject: [ticket/11638] generate_text_for_display on viewtopic.php lines: 1403-1417 sub-task of ticket PHPBB3-11635: find and fix all bypasses of generate_text_for_* PHPBB3-11638 --- phpBB/viewtopic.php | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) (limited to 'phpBB') diff --git a/phpBB/viewtopic.php b/phpBB/viewtopic.php index 34586b1491..9274539ab4 100644 --- a/phpBB/viewtopic.php +++ b/phpBB/viewtopic.php @@ -1385,16 +1385,7 @@ for ($i = 0, $end = sizeof($post_list); $i < $end; ++$i) } // Parse the message and subject - $message = censor_text($row['post_text']); - - // Second parse bbcode here - if ($row['bbcode_bitfield']) - { - $bbcode->bbcode_second_pass($message, $row['bbcode_uid'], $row['bbcode_bitfield']); - } - - $message = bbcode_nl2br($message); - $message = smiley_text($message); + $message = generate_text_for_display($message, $row['bbcode_uid'], $row['bbcode_bitfield'], ($row['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES, true); if (!empty($attachments[$row['post_id']])) { -- cgit v1.2.1 From 176729ab001300c295695537803f389e175049cf Mon Sep 17 00:00:00 2001 From: Nathaniel Guse Date: Sat, 13 Jul 2013 17:50:35 -0500 Subject: [ticket/11692] Don't update search_type in dev migration if already appended PHPBB3-11692 --- phpBB/includes/db/migration/data/310/dev.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'phpBB') diff --git a/phpBB/includes/db/migration/data/310/dev.php b/phpBB/includes/db/migration/data/310/dev.php index 13b36bbf30..0fc2950987 100644 --- a/phpBB/includes/db/migration/data/310/dev.php +++ b/phpBB/includes/db/migration/data/310/dev.php @@ -82,7 +82,10 @@ class phpbb_db_migration_data_310_dev extends phpbb_db_migration public function update_data() { return array( - array('config.update', array('search_type', 'phpbb_search_' . $this->config['search_type'])), + array('if', array( + (strpos('phpbb_search_', $this->config['search_type']) !== 0), + array('config.update', array('search_type', 'phpbb_search_' . $this->config['search_type'])), + )), array('config.add', array('fulltext_postgres_ts_name', 'simple')), array('config.add', array('fulltext_postgres_min_word_len', 4)), -- cgit v1.2.1 From d6de892ee40579c45259f8c68ba7eef26accc08b Mon Sep 17 00:00:00 2001 From: Igor Wiedler Date: Sat, 13 Jul 2013 16:47:15 -0400 Subject: [ticket/11574] Fix various path issues in the updater PHPBB3-11574 --- phpBB/includes/functions_container.php | 26 +++++++++++++++++++++++++- phpBB/install/database_update.php | 5 ++--- phpBB/install/install_update.php | 28 ++-------------------------- 3 files changed, 29 insertions(+), 30 deletions(-) (limited to 'phpBB') diff --git a/phpBB/includes/functions_container.php b/phpBB/includes/functions_container.php index d302b75350..923a8f370b 100644 --- a/phpBB/includes/functions_container.php +++ b/phpBB/includes/functions_container.php @@ -53,7 +53,7 @@ function phpbb_create_container(array $extensions, $phpbb_root_path, $php_ext) */ function phpbb_create_install_container($phpbb_root_path, $php_ext) { - $other_config_path = $phpbb_root_path . 'install/update/new/config'; + $other_config_path = $phpbb_root_path . 'install/update/new/config/'; $config_path = file_exists($other_config_path . 'services.yml') ? $other_config_path : $phpbb_root_path . 'config'; $core = new phpbb_di_extension_core($config_path); @@ -73,6 +73,30 @@ function phpbb_create_install_container($phpbb_root_path, $php_ext) return $container; } +/** +* Create updater container +* +* @param string $phpbb_root_path Root path +* @param string $php_ext PHP Extension +* @param array $config_path Path to config directory +* @return ContainerBuilder object (compiled) +*/ +function phpbb_create_update_container($phpbb_root_path, $php_ext, $config_path) +{ + return phpbb_create_compiled_container( + array( + new phpbb_di_extension_config($phpbb_root_path . 'config.' . $php_ext), + new phpbb_di_extension_core($config_path), + ), + array( + new phpbb_di_pass_collection_pass(), + new phpbb_di_pass_kernel_pass(), + ), + $phpbb_root_path, + $php_ext + ); +} + /** * Create a compiled ContainerBuilder object * diff --git a/phpBB/install/database_update.php b/phpBB/install/database_update.php index b0e28958ac..51ffdd3c88 100644 --- a/phpBB/install/database_update.php +++ b/phpBB/install/database_update.php @@ -121,8 +121,8 @@ $phpbb_class_loader = new phpbb_class_loader('phpbb_', "{$phpbb_root_path}includ $phpbb_class_loader->register(); // Set up container (must be done here because extensions table may not exist) -$other_config_path = $phpbb_root_path . 'install/update/new/config'; -$config_path = file_exists($other_config_path . 'services.yml') ? $other_config_path : $phpbb_root_path; +$other_config_path = $phpbb_root_path . 'install/update/new/config/'; +$config_path = file_exists($other_config_path . 'services.yml') ? $other_config_path : $phpbb_root_path . 'config'; $container_extensions = array( new phpbb_di_extension_config($phpbb_root_path . 'config.' . $phpEx), @@ -130,7 +130,6 @@ $container_extensions = array( ); $container_passes = array( new phpbb_di_pass_collection_pass(), - //new phpbb_di_pass_kernel_pass(), ); $phpbb_container = phpbb_create_container($container_extensions, $phpbb_root_path, $phpEx); diff --git a/phpBB/install/install_update.php b/phpBB/install/install_update.php index 38d9f66629..f9dfaaef50 100644 --- a/phpBB/install/install_update.php +++ b/phpBB/install/install_update.php @@ -75,7 +75,7 @@ class install_update extends module global $request, $phpbb_admin_path, $phpbb_adm_relative_path, $phpbb_container; // Create a normal container now - $phpbb_container = phpbb_create_default_container($phpbb_root_path, $phpEx); + $phpbb_container = phpbb_create_update_container($phpbb_root_path, $phpEx, $phpbb_root_path . 'install/update/new/config'); // Writes into global $cache $cache = $phpbb_container->get('cache'); @@ -125,7 +125,7 @@ class install_update extends module $config['default_lang'] = $language; $user->data['user_lang'] = $language; - $user->setup(array('common', 'acp/common', 'acp/board', 'install', 'posting')); + $user->add_lang(array('common', 'acp/common', 'acp/board', 'install', 'posting')); // Reset the default_lang $config['default_lang'] = $config_default_lang; @@ -302,30 +302,6 @@ class install_update extends module break; case 'update_db': - - // Make sure the database update is valid for the latest version - $valid = false; - $updates_to_version = ''; - - if (file_exists($phpbb_root_path . 'install/database_update.' . $phpEx)) - { - include_once($phpbb_root_path . 'install/database_update.' . $phpEx); - - if ($updates_to_version === $this->update_info['version']['to']) - { - $valid = true; - } - } - - // Should not happen at all - if (!$valid) - { - trigger_error($user->lang['DATABASE_UPDATE_INFO_OLD'], E_USER_ERROR); - } - - // Just a precaution - $cache->purge(); - // Redirect the user to the database update script with some explanations... $template->assign_vars(array( 'S_DB_UPDATE' => true, -- cgit v1.2.1 From c96f8936c969b3fd441c179cc4ffa8ea75d8f901 Mon Sep 17 00:00:00 2001 From: Igor Wiedler Date: Sat, 13 Jul 2013 18:19:21 -0400 Subject: [ticket/11574] Fix table prefix in database updater PHPBB3-11574 --- phpBB/install/database_update.php | 3 ++- phpBB/install/index.php | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'phpBB') diff --git a/phpBB/install/database_update.php b/phpBB/install/database_update.php index 51ffdd3c88..5f2d46c7ad 100644 --- a/phpBB/install/database_update.php +++ b/phpBB/install/database_update.php @@ -25,7 +25,7 @@ if (!function_exists('phpbb_require_updated')) { function phpbb_require_updated($path, $optional = false) { - global $phpbb_root_path; + global $phpbb_root_path, $table_prefix; $new_path = $phpbb_root_path . 'install/update/new/' . $path; $old_path = $phpbb_root_path . $path; @@ -108,6 +108,7 @@ phpbb_require_updated('includes/functions.' . $phpEx); phpbb_require_updated('includes/functions_content.' . $phpEx); phpbb_require_updated('includes/functions_container.' . $phpEx); +require($phpbb_root_path . 'config.' . $phpEx); phpbb_require_updated('includes/constants.' . $phpEx); phpbb_require_updated('includes/utf/utf_tools.' . $phpEx); diff --git a/phpBB/install/index.php b/phpBB/install/index.php index 4051a5a08b..055fb72d7c 100644 --- a/phpBB/install/index.php +++ b/phpBB/install/index.php @@ -28,7 +28,7 @@ if (version_compare(PHP_VERSION, '5.3.3') < 0) function phpbb_require_updated($path, $optional = false) { - global $phpbb_root_path; + global $phpbb_root_path, $table_prefix; $new_path = $phpbb_root_path . 'install/update/new/' . $path; $old_path = $phpbb_root_path . $path; -- cgit v1.2.1 From e6e2a50062eca8b640822b8f616ea4bb23b23566 Mon Sep 17 00:00:00 2001 From: Igor Wiedler Date: Sun, 14 Jul 2013 01:28:49 -0400 Subject: [ticket/11574] Add trailing slash for consistency PHPBB3-11574 --- phpBB/includes/functions_container.php | 2 +- phpBB/install/database_update.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'phpBB') diff --git a/phpBB/includes/functions_container.php b/phpBB/includes/functions_container.php index 923a8f370b..e22fa9919a 100644 --- a/phpBB/includes/functions_container.php +++ b/phpBB/includes/functions_container.php @@ -54,7 +54,7 @@ function phpbb_create_container(array $extensions, $phpbb_root_path, $php_ext) function phpbb_create_install_container($phpbb_root_path, $php_ext) { $other_config_path = $phpbb_root_path . 'install/update/new/config/'; - $config_path = file_exists($other_config_path . 'services.yml') ? $other_config_path : $phpbb_root_path . 'config'; + $config_path = file_exists($other_config_path . 'services.yml') ? $other_config_path : $phpbb_root_path . 'config/'; $core = new phpbb_di_extension_core($config_path); $container = phpbb_create_container(array($core), $phpbb_root_path, $php_ext); diff --git a/phpBB/install/database_update.php b/phpBB/install/database_update.php index 5f2d46c7ad..e0ecc242e1 100644 --- a/phpBB/install/database_update.php +++ b/phpBB/install/database_update.php @@ -123,7 +123,7 @@ $phpbb_class_loader->register(); // Set up container (must be done here because extensions table may not exist) $other_config_path = $phpbb_root_path . 'install/update/new/config/'; -$config_path = file_exists($other_config_path . 'services.yml') ? $other_config_path : $phpbb_root_path . 'config'; +$config_path = file_exists($other_config_path . 'services.yml') ? $other_config_path : $phpbb_root_path . 'config/'; $container_extensions = array( new phpbb_di_extension_config($phpbb_root_path . 'config.' . $phpEx), -- cgit v1.2.1 From 7030578bbe9e11c18b5becaf8b06e670e3c2e3cd Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Sun, 14 Jul 2013 01:32:34 -0400 Subject: [ticket/11698] Moving all autoloadable files to phpbb/ PHPBB3-11698 --- phpBB/common.php | 6 +- phpBB/includes/acp/acp_search.php | 2 +- phpBB/includes/auth/auth.php | 1080 ------------ phpBB/includes/auth/index.htm | 10 - phpBB/includes/auth/provider/apache.php | 259 --- phpBB/includes/auth/provider/base.php | 72 - phpBB/includes/auth/provider/db.php | 297 ---- phpBB/includes/auth/provider/index.htm | 10 - phpBB/includes/auth/provider/interface.php | 105 -- phpBB/includes/auth/provider/ldap.php | 346 ---- phpBB/includes/avatar/driver/driver.php | 138 -- phpBB/includes/avatar/driver/gravatar.php | 172 -- phpBB/includes/avatar/driver/interface.php | 116 -- phpBB/includes/avatar/driver/local.php | 197 --- phpBB/includes/avatar/driver/remote.php | 164 -- phpBB/includes/avatar/driver/upload.php | 185 -- phpBB/includes/avatar/manager.php | 309 ---- phpBB/includes/cache/driver/apc.php | 75 - phpBB/includes/cache/driver/base.php | 23 - phpBB/includes/cache/driver/eaccelerator.php | 112 -- phpBB/includes/cache/driver/file.php | 740 -------- phpBB/includes/cache/driver/interface.php | 144 -- phpBB/includes/cache/driver/memcache.php | 129 -- phpBB/includes/cache/driver/memory.php | 439 ----- phpBB/includes/cache/driver/null.php | 154 -- phpBB/includes/cache/driver/redis.php | 166 -- phpBB/includes/cache/driver/wincache.php | 78 - phpBB/includes/cache/driver/xcache.php | 112 -- phpBB/includes/cache/service.php | 406 ----- phpBB/includes/class_loader.php | 170 -- phpBB/includes/config/config.php | 170 -- phpBB/includes/config/db.php | 207 --- phpBB/includes/config/db_text.php | 163 -- phpBB/includes/content_visibility.php | 651 ------- phpBB/includes/controller/exception.php | 24 - phpBB/includes/controller/helper.php | 139 -- phpBB/includes/controller/provider.php | 82 - phpBB/includes/controller/resolver.php | 154 -- phpBB/includes/cron/manager.php | 138 -- phpBB/includes/cron/task/base.php | 76 - phpBB/includes/cron/task/core/prune_all_forums.php | 93 - phpBB/includes/cron/task/core/prune_forum.php | 163 -- phpBB/includes/cron/task/core/queue.php | 82 - phpBB/includes/cron/task/core/tidy_cache.php | 76 - phpBB/includes/cron/task/core/tidy_database.php | 70 - phpBB/includes/cron/task/core/tidy_search.php | 109 -- phpBB/includes/cron/task/core/tidy_sessions.php | 63 - phpBB/includes/cron/task/core/tidy_warnings.php | 84 - phpBB/includes/cron/task/parametrized.php | 52 - phpBB/includes/cron/task/task.php | 55 - phpBB/includes/cron/task/wrapper.php | 108 -- phpBB/includes/datetime.php | 158 -- phpBB/includes/db/driver/driver.php | 1044 ----------- phpBB/includes/db/driver/firebird.php | 538 ------ phpBB/includes/db/driver/mssql.php | 472 ----- phpBB/includes/db/driver/mssql_base.php | 65 - phpBB/includes/db/driver/mssql_odbc.php | 383 ---- phpBB/includes/db/driver/mssqlnative.php | 613 ------- phpBB/includes/db/driver/mysql.php | 472 ----- phpBB/includes/db/driver/mysql_base.php | 145 -- phpBB/includes/db/driver/mysqli.php | 463 ----- phpBB/includes/db/driver/oracle.php | 803 --------- phpBB/includes/db/driver/postgres.php | 491 ------ phpBB/includes/db/driver/sqlite.php | 365 ---- phpBB/includes/db/migration/data/30x/3_0_1.php | 28 - phpBB/includes/db/migration/data/30x/3_0_10.php | 28 - .../includes/db/migration/data/30x/3_0_10_rc1.php | 30 - .../includes/db/migration/data/30x/3_0_10_rc2.php | 28 - .../includes/db/migration/data/30x/3_0_10_rc3.php | 28 - phpBB/includes/db/migration/data/30x/3_0_11.php | 28 - .../includes/db/migration/data/30x/3_0_11_rc1.php | 95 - .../includes/db/migration/data/30x/3_0_11_rc2.php | 50 - .../includes/db/migration/data/30x/3_0_12_rc1.php | 123 -- phpBB/includes/db/migration/data/30x/3_0_1_rc1.php | 108 -- phpBB/includes/db/migration/data/30x/3_0_2.php | 28 - phpBB/includes/db/migration/data/30x/3_0_2_rc1.php | 32 - phpBB/includes/db/migration/data/30x/3_0_2_rc2.php | 80 - phpBB/includes/db/migration/data/30x/3_0_3.php | 28 - phpBB/includes/db/migration/data/30x/3_0_3_rc1.php | 83 - phpBB/includes/db/migration/data/30x/3_0_4.php | 49 - phpBB/includes/db/migration/data/30x/3_0_4_rc1.php | 123 -- phpBB/includes/db/migration/data/30x/3_0_5.php | 28 - phpBB/includes/db/migration/data/30x/3_0_5_rc1.php | 124 -- .../db/migration/data/30x/3_0_5_rc1part2.php | 42 - phpBB/includes/db/migration/data/30x/3_0_6.php | 28 - phpBB/includes/db/migration/data/30x/3_0_6_rc1.php | 324 ---- phpBB/includes/db/migration/data/30x/3_0_6_rc2.php | 28 - phpBB/includes/db/migration/data/30x/3_0_6_rc3.php | 40 - phpBB/includes/db/migration/data/30x/3_0_6_rc4.php | 28 - phpBB/includes/db/migration/data/30x/3_0_7.php | 28 - phpBB/includes/db/migration/data/30x/3_0_7_pl1.php | 28 - phpBB/includes/db/migration/data/30x/3_0_7_rc1.php | 76 - phpBB/includes/db/migration/data/30x/3_0_7_rc2.php | 73 - phpBB/includes/db/migration/data/30x/3_0_8.php | 28 - phpBB/includes/db/migration/data/30x/3_0_8_rc1.php | 221 --- phpBB/includes/db/migration/data/30x/3_0_9.php | 28 - phpBB/includes/db/migration/data/30x/3_0_9_rc1.php | 124 -- phpBB/includes/db/migration/data/30x/3_0_9_rc2.php | 28 - phpBB/includes/db/migration/data/30x/3_0_9_rc3.php | 28 - phpBB/includes/db/migration/data/30x/3_0_9_rc4.php | 28 - .../db/migration/data/30x/local_url_bbcode.php | 57 - phpBB/includes/db/migration/data/310/avatars.php | 67 - .../includes/db/migration/data/310/boardindex.php | 23 - .../db/migration/data/310/config_db_text.php | 45 - phpBB/includes/db/migration/data/310/dev.php | 408 ----- .../includes/db/migration/data/310/extensions.php | 69 - .../db/migration/data/310/forgot_password.php | 28 - .../db/migration/data/310/jquery_update.php | 31 - .../data/310/notification_options_reconvert.php | 118 -- .../db/migration/data/310/notifications.php | 96 -- .../data/310/notifications_schema_fix.php | 92 - .../migration/data/310/reported_posts_display.php | 47 - .../migration/data/310/signature_module_auth.php | 51 - .../db/migration/data/310/softdelete_p1.php | 171 -- .../db/migration/data/310/softdelete_p2.php | 68 - .../db/migration/data/310/style_update_p1.php | 185 -- .../db/migration/data/310/style_update_p2.php | 129 -- phpBB/includes/db/migration/data/310/teampage.php | 104 -- phpBB/includes/db/migration/data/310/timezone.php | 163 -- .../includes/db/migration/data/310/timezone_p2.php | 43 - phpBB/includes/db/migration/exception.php | 79 - phpBB/includes/db/migration/migration.php | 190 -- phpBB/includes/db/migration/tool/config.php | 150 -- phpBB/includes/db/migration/tool/interface.php | 33 - phpBB/includes/db/migration/tool/module.php | 501 ------ phpBB/includes/db/migration/tool/permission.php | 622 ------- phpBB/includes/db/migrator.php | 746 -------- phpBB/includes/db/sql_insert_buffer.php | 150 -- phpBB/includes/di/extension/config.php | 84 - phpBB/includes/di/extension/core.php | 69 - phpBB/includes/di/extension/ext.php | 69 - phpBB/includes/di/pass/collection_pass.php | 46 - phpBB/includes/di/pass/kernel_pass.php | 68 - phpBB/includes/di/service_collection.php | 49 - phpBB/includes/error_collector.php | 62 - phpBB/includes/event/data.php | 68 - phpBB/includes/event/dispatcher.php | 42 - .../includes/event/extension_subscriber_loader.php | 46 - .../includes/event/kernel_exception_subscriber.php | 85 - phpBB/includes/event/kernel_request_subscriber.php | 83 - .../includes/event/kernel_terminate_subscriber.php | 43 - phpBB/includes/extension/base.php | 135 -- phpBB/includes/extension/exception.php | 27 - phpBB/includes/extension/finder.php | 523 ------ phpBB/includes/extension/interface.php | 67 - phpBB/includes/extension/manager.php | 513 ------ phpBB/includes/extension/metadata_manager.php | 371 ---- phpBB/includes/extension/provider.php | 76 - phpBB/includes/feed/base.php | 261 --- phpBB/includes/feed/factory.php | 129 -- phpBB/includes/feed/forum.php | 145 -- phpBB/includes/feed/forums.php | 72 - phpBB/includes/feed/helper.php | 159 -- phpBB/includes/feed/news.php | 112 -- phpBB/includes/feed/overall.php | 90 - phpBB/includes/feed/post_base.php | 57 - phpBB/includes/feed/topic.php | 116 -- phpBB/includes/feed/topic_base.php | 59 - phpBB/includes/feed/topics.php | 91 - phpBB/includes/feed/topics_active.php | 136 -- phpBB/includes/filesystem.php | 52 - phpBB/includes/groupposition/exception.php | 23 - phpBB/includes/groupposition/interface.php | 84 - phpBB/includes/groupposition/legend.php | 251 --- phpBB/includes/groupposition/teampage.php | 604 ------- phpBB/includes/hook/finder.php | 84 - phpBB/includes/json_response.php | 41 - phpBB/includes/lock/db.php | 149 -- phpBB/includes/lock/flock.php | 144 -- phpBB/includes/log/interface.php | 106 -- phpBB/includes/log/log.php | 739 -------- phpBB/includes/notification/exception.php | 29 - phpBB/includes/notification/manager.php | 910 ---------- phpBB/includes/notification/method/base.php | 116 -- phpBB/includes/notification/method/email.php | 52 - phpBB/includes/notification/method/interface.php | 48 - phpBB/includes/notification/method/jabber.php | 69 - .../notification/method/messenger_base.php | 100 -- phpBB/includes/notification/type/approve_post.php | 140 -- phpBB/includes/notification/type/approve_topic.php | 138 -- phpBB/includes/notification/type/base.php | 489 ------ phpBB/includes/notification/type/bookmark.php | 138 -- .../includes/notification/type/disapprove_post.php | 120 -- .../notification/type/disapprove_topic.php | 120 -- phpBB/includes/notification/type/interface.php | 189 -- phpBB/includes/notification/type/pm.php | 184 -- phpBB/includes/notification/type/post.php | 385 ----- phpBB/includes/notification/type/post_in_queue.php | 154 -- phpBB/includes/notification/type/quote.php | 222 --- phpBB/includes/notification/type/report_pm.php | 229 --- .../notification/type/report_pm_closed.php | 155 -- phpBB/includes/notification/type/report_post.php | 196 --- .../notification/type/report_post_closed.php | 155 -- phpBB/includes/notification/type/topic.php | 277 --- .../includes/notification/type/topic_in_queue.php | 154 -- phpBB/includes/php/ini.php | 175 -- .../includes/request/deactivated_super_global.php | 121 -- phpBB/includes/request/interface.php | 139 -- phpBB/includes/request/request.php | 415 ----- phpBB/includes/request/type_cast_helper.php | 194 --- .../request/type_cast_helper_interface.php | 63 - phpBB/includes/search/base.php | 330 ---- phpBB/includes/search/fulltext_mysql.php | 948 ---------- phpBB/includes/search/fulltext_native.php | 1821 -------------------- phpBB/includes/search/fulltext_postgres.php | 963 ----------- phpBB/includes/search/fulltext_sphinx.php | 917 ---------- phpBB/includes/search/index.htm | 10 - phpBB/includes/search/sphinx/config.php | 288 ---- phpBB/includes/search/sphinx/config_comment.php | 49 - phpBB/includes/search/sphinx/config_section.php | 162 -- phpBB/includes/search/sphinx/config_variable.php | 80 - phpBB/includes/session.php | 1516 ---------------- phpBB/includes/style/extension_path_provider.php | 137 -- phpBB/includes/style/path_provider.php | 62 - phpBB/includes/style/path_provider_interface.php | 42 - phpBB/includes/style/resource_locator.php | 348 ---- phpBB/includes/style/style.php | 241 --- phpBB/includes/template/asset.php | 182 -- phpBB/includes/template/context.php | 389 ----- phpBB/includes/template/locator.php | 163 -- phpBB/includes/template/template.php | 157 -- phpBB/includes/template/twig/definition.php | 69 - phpBB/includes/template/twig/environment.php | 140 -- phpBB/includes/template/twig/extension.php | 188 -- phpBB/includes/template/twig/lexer.php | 300 ---- phpBB/includes/template/twig/node/define.php | 58 - phpBB/includes/template/twig/node/event.php | 79 - .../twig/node/expression/binary/equalequal.php | 25 - .../twig/node/expression/binary/notequalequal.php | 25 - phpBB/includes/template/twig/node/include.php | 56 - phpBB/includes/template/twig/node/includeasset.php | 60 - phpBB/includes/template/twig/node/includecss.php | 30 - phpBB/includes/template/twig/node/includejs.php | 32 - phpBB/includes/template/twig/node/includephp.php | 91 - phpBB/includes/template/twig/node/php.php | 55 - .../includes/template/twig/tokenparser/define.php | 66 - phpBB/includes/template/twig/tokenparser/event.php | 47 - .../includes/template/twig/tokenparser/include.php | 46 - .../template/twig/tokenparser/includecss.php | 38 - .../template/twig/tokenparser/includejs.php | 47 - .../template/twig/tokenparser/includephp.php | 56 - phpBB/includes/template/twig/tokenparser/php.php | 55 - phpBB/includes/template/twig/twig.php | 465 ----- phpBB/includes/tree/interface.php | 122 -- phpBB/includes/tree/nestedset.php | 850 --------- phpBB/includes/tree/nestedset_forum.php | 46 - phpBB/includes/user.php | 857 --------- phpBB/includes/user_loader.php | 231 --- phpBB/install/database_update.php | 6 +- phpBB/install/index.php | 4 +- phpBB/install/install_install.php | 6 +- phpBB/phpbb/auth/auth.php | 1080 ++++++++++++ phpBB/phpbb/auth/index.htm | 10 + phpBB/phpbb/auth/provider/apache.php | 259 +++ phpBB/phpbb/auth/provider/base.php | 72 + phpBB/phpbb/auth/provider/db.php | 297 ++++ phpBB/phpbb/auth/provider/index.htm | 10 + phpBB/phpbb/auth/provider/interface.php | 105 ++ phpBB/phpbb/auth/provider/ldap.php | 346 ++++ phpBB/phpbb/avatar/driver/driver.php | 138 ++ phpBB/phpbb/avatar/driver/gravatar.php | 172 ++ phpBB/phpbb/avatar/driver/interface.php | 116 ++ phpBB/phpbb/avatar/driver/local.php | 197 +++ phpBB/phpbb/avatar/driver/remote.php | 164 ++ phpBB/phpbb/avatar/driver/upload.php | 185 ++ phpBB/phpbb/avatar/manager.php | 309 ++++ phpBB/phpbb/cache/driver/apc.php | 75 + phpBB/phpbb/cache/driver/base.php | 23 + phpBB/phpbb/cache/driver/eaccelerator.php | 112 ++ phpBB/phpbb/cache/driver/file.php | 740 ++++++++ phpBB/phpbb/cache/driver/interface.php | 144 ++ phpBB/phpbb/cache/driver/memcache.php | 129 ++ phpBB/phpbb/cache/driver/memory.php | 439 +++++ phpBB/phpbb/cache/driver/null.php | 154 ++ phpBB/phpbb/cache/driver/redis.php | 166 ++ phpBB/phpbb/cache/driver/wincache.php | 78 + phpBB/phpbb/cache/driver/xcache.php | 112 ++ phpBB/phpbb/cache/service.php | 406 +++++ phpBB/phpbb/class_loader.php | 170 ++ phpBB/phpbb/config/config.php | 170 ++ phpBB/phpbb/config/db.php | 207 +++ phpBB/phpbb/config/db_text.php | 163 ++ phpBB/phpbb/content_visibility.php | 651 +++++++ phpBB/phpbb/controller/exception.php | 24 + phpBB/phpbb/controller/helper.php | 139 ++ phpBB/phpbb/controller/provider.php | 82 + phpBB/phpbb/controller/resolver.php | 154 ++ phpBB/phpbb/cron/manager.php | 138 ++ phpBB/phpbb/cron/task/base.php | 76 + phpBB/phpbb/cron/task/core/prune_all_forums.php | 93 + phpBB/phpbb/cron/task/core/prune_forum.php | 163 ++ phpBB/phpbb/cron/task/core/queue.php | 82 + phpBB/phpbb/cron/task/core/tidy_cache.php | 76 + phpBB/phpbb/cron/task/core/tidy_database.php | 70 + phpBB/phpbb/cron/task/core/tidy_search.php | 104 ++ phpBB/phpbb/cron/task/core/tidy_sessions.php | 63 + phpBB/phpbb/cron/task/core/tidy_warnings.php | 84 + phpBB/phpbb/cron/task/parametrized.php | 52 + phpBB/phpbb/cron/task/task.php | 55 + phpBB/phpbb/cron/task/wrapper.php | 108 ++ phpBB/phpbb/datetime.php | 158 ++ phpBB/phpbb/db/driver/driver.php | 1044 +++++++++++ phpBB/phpbb/db/driver/firebird.php | 538 ++++++ phpBB/phpbb/db/driver/mssql.php | 472 +++++ phpBB/phpbb/db/driver/mssql_base.php | 65 + phpBB/phpbb/db/driver/mssql_odbc.php | 383 ++++ phpBB/phpbb/db/driver/mssqlnative.php | 613 +++++++ phpBB/phpbb/db/driver/mysql.php | 472 +++++ phpBB/phpbb/db/driver/mysql_base.php | 145 ++ phpBB/phpbb/db/driver/mysqli.php | 463 +++++ phpBB/phpbb/db/driver/oracle.php | 803 +++++++++ phpBB/phpbb/db/driver/postgres.php | 491 ++++++ phpBB/phpbb/db/driver/sqlite.php | 365 ++++ phpBB/phpbb/db/migration/data/30x/3_0_1.php | 28 + phpBB/phpbb/db/migration/data/30x/3_0_10.php | 28 + phpBB/phpbb/db/migration/data/30x/3_0_10_rc1.php | 30 + phpBB/phpbb/db/migration/data/30x/3_0_10_rc2.php | 28 + phpBB/phpbb/db/migration/data/30x/3_0_10_rc3.php | 28 + phpBB/phpbb/db/migration/data/30x/3_0_11.php | 28 + phpBB/phpbb/db/migration/data/30x/3_0_11_rc1.php | 95 + phpBB/phpbb/db/migration/data/30x/3_0_11_rc2.php | 50 + phpBB/phpbb/db/migration/data/30x/3_0_12_rc1.php | 123 ++ phpBB/phpbb/db/migration/data/30x/3_0_1_rc1.php | 108 ++ phpBB/phpbb/db/migration/data/30x/3_0_2.php | 28 + phpBB/phpbb/db/migration/data/30x/3_0_2_rc1.php | 32 + phpBB/phpbb/db/migration/data/30x/3_0_2_rc2.php | 80 + phpBB/phpbb/db/migration/data/30x/3_0_3.php | 28 + phpBB/phpbb/db/migration/data/30x/3_0_3_rc1.php | 83 + phpBB/phpbb/db/migration/data/30x/3_0_4.php | 49 + phpBB/phpbb/db/migration/data/30x/3_0_4_rc1.php | 123 ++ phpBB/phpbb/db/migration/data/30x/3_0_5.php | 28 + phpBB/phpbb/db/migration/data/30x/3_0_5_rc1.php | 124 ++ .../phpbb/db/migration/data/30x/3_0_5_rc1part2.php | 42 + phpBB/phpbb/db/migration/data/30x/3_0_6.php | 28 + phpBB/phpbb/db/migration/data/30x/3_0_6_rc1.php | 324 ++++ phpBB/phpbb/db/migration/data/30x/3_0_6_rc2.php | 28 + phpBB/phpbb/db/migration/data/30x/3_0_6_rc3.php | 40 + phpBB/phpbb/db/migration/data/30x/3_0_6_rc4.php | 28 + phpBB/phpbb/db/migration/data/30x/3_0_7.php | 28 + phpBB/phpbb/db/migration/data/30x/3_0_7_pl1.php | 28 + phpBB/phpbb/db/migration/data/30x/3_0_7_rc1.php | 76 + phpBB/phpbb/db/migration/data/30x/3_0_7_rc2.php | 73 + phpBB/phpbb/db/migration/data/30x/3_0_8.php | 28 + phpBB/phpbb/db/migration/data/30x/3_0_8_rc1.php | 221 +++ phpBB/phpbb/db/migration/data/30x/3_0_9.php | 28 + phpBB/phpbb/db/migration/data/30x/3_0_9_rc1.php | 124 ++ phpBB/phpbb/db/migration/data/30x/3_0_9_rc2.php | 28 + phpBB/phpbb/db/migration/data/30x/3_0_9_rc3.php | 28 + phpBB/phpbb/db/migration/data/30x/3_0_9_rc4.php | 28 + .../db/migration/data/30x/local_url_bbcode.php | 57 + phpBB/phpbb/db/migration/data/310/avatars.php | 67 + phpBB/phpbb/db/migration/data/310/boardindex.php | 23 + .../phpbb/db/migration/data/310/config_db_text.php | 45 + phpBB/phpbb/db/migration/data/310/dev.php | 408 +++++ phpBB/phpbb/db/migration/data/310/extensions.php | 69 + .../db/migration/data/310/forgot_password.php | 28 + .../phpbb/db/migration/data/310/jquery_update.php | 31 + .../data/310/notification_options_reconvert.php | 118 ++ .../phpbb/db/migration/data/310/notifications.php | 96 ++ .../data/310/notifications_schema_fix.php | 92 + .../migration/data/310/reported_posts_display.php | 47 + .../migration/data/310/signature_module_auth.php | 51 + .../phpbb/db/migration/data/310/softdelete_p1.php | 171 ++ .../phpbb/db/migration/data/310/softdelete_p2.php | 68 + .../db/migration/data/310/style_update_p1.php | 185 ++ .../db/migration/data/310/style_update_p2.php | 129 ++ phpBB/phpbb/db/migration/data/310/teampage.php | 104 ++ phpBB/phpbb/db/migration/data/310/timezone.php | 163 ++ phpBB/phpbb/db/migration/data/310/timezone_p2.php | 43 + phpBB/phpbb/db/migration/exception.php | 79 + phpBB/phpbb/db/migration/migration.php | 190 ++ phpBB/phpbb/db/migration/tool/config.php | 150 ++ phpBB/phpbb/db/migration/tool/interface.php | 33 + phpBB/phpbb/db/migration/tool/module.php | 501 ++++++ phpBB/phpbb/db/migration/tool/permission.php | 622 +++++++ phpBB/phpbb/db/migrator.php | 746 ++++++++ phpBB/phpbb/db/sql_insert_buffer.php | 150 ++ phpBB/phpbb/di/extension/config.php | 84 + phpBB/phpbb/di/extension/core.php | 69 + phpBB/phpbb/di/extension/ext.php | 69 + phpBB/phpbb/di/pass/collection_pass.php | 46 + phpBB/phpbb/di/pass/kernel_pass.php | 68 + phpBB/phpbb/di/service_collection.php | 49 + phpBB/phpbb/error_collector.php | 62 + phpBB/phpbb/event/data.php | 68 + phpBB/phpbb/event/dispatcher.php | 42 + phpBB/phpbb/event/extension_subscriber_loader.php | 46 + phpBB/phpbb/event/kernel_exception_subscriber.php | 85 + phpBB/phpbb/event/kernel_request_subscriber.php | 83 + phpBB/phpbb/event/kernel_terminate_subscriber.php | 43 + phpBB/phpbb/extension/base.php | 135 ++ phpBB/phpbb/extension/exception.php | 27 + phpBB/phpbb/extension/finder.php | 523 ++++++ phpBB/phpbb/extension/interface.php | 67 + phpBB/phpbb/extension/manager.php | 513 ++++++ phpBB/phpbb/extension/metadata_manager.php | 371 ++++ phpBB/phpbb/extension/provider.php | 76 + phpBB/phpbb/feed/base.php | 261 +++ phpBB/phpbb/feed/factory.php | 129 ++ phpBB/phpbb/feed/forum.php | 145 ++ phpBB/phpbb/feed/forums.php | 72 + phpBB/phpbb/feed/helper.php | 159 ++ phpBB/phpbb/feed/news.php | 112 ++ phpBB/phpbb/feed/overall.php | 90 + phpBB/phpbb/feed/post_base.php | 57 + phpBB/phpbb/feed/topic.php | 116 ++ phpBB/phpbb/feed/topic_base.php | 59 + phpBB/phpbb/feed/topics.php | 91 + phpBB/phpbb/feed/topics_active.php | 136 ++ phpBB/phpbb/filesystem.php | 52 + phpBB/phpbb/groupposition/exception.php | 23 + phpBB/phpbb/groupposition/interface.php | 84 + phpBB/phpbb/groupposition/legend.php | 251 +++ phpBB/phpbb/groupposition/teampage.php | 604 +++++++ phpBB/phpbb/hook/finder.php | 84 + phpBB/phpbb/json_response.php | 41 + phpBB/phpbb/lock/db.php | 149 ++ phpBB/phpbb/lock/flock.php | 144 ++ phpBB/phpbb/log/interface.php | 106 ++ phpBB/phpbb/log/log.php | 739 ++++++++ phpBB/phpbb/notification/exception.php | 29 + phpBB/phpbb/notification/manager.php | 910 ++++++++++ phpBB/phpbb/notification/method/base.php | 116 ++ phpBB/phpbb/notification/method/email.php | 52 + phpBB/phpbb/notification/method/interface.php | 48 + phpBB/phpbb/notification/method/jabber.php | 69 + phpBB/phpbb/notification/method/messenger_base.php | 100 ++ phpBB/phpbb/notification/type/approve_post.php | 140 ++ phpBB/phpbb/notification/type/approve_topic.php | 138 ++ phpBB/phpbb/notification/type/base.php | 489 ++++++ phpBB/phpbb/notification/type/bookmark.php | 138 ++ phpBB/phpbb/notification/type/disapprove_post.php | 120 ++ phpBB/phpbb/notification/type/disapprove_topic.php | 120 ++ phpBB/phpbb/notification/type/interface.php | 189 ++ phpBB/phpbb/notification/type/pm.php | 184 ++ phpBB/phpbb/notification/type/post.php | 385 +++++ phpBB/phpbb/notification/type/post_in_queue.php | 154 ++ phpBB/phpbb/notification/type/quote.php | 222 +++ phpBB/phpbb/notification/type/report_pm.php | 229 +++ phpBB/phpbb/notification/type/report_pm_closed.php | 155 ++ phpBB/phpbb/notification/type/report_post.php | 196 +++ .../phpbb/notification/type/report_post_closed.php | 155 ++ phpBB/phpbb/notification/type/topic.php | 277 +++ phpBB/phpbb/notification/type/topic_in_queue.php | 154 ++ phpBB/phpbb/php/ini.php | 175 ++ phpBB/phpbb/request/deactivated_super_global.php | 121 ++ phpBB/phpbb/request/interface.php | 139 ++ phpBB/phpbb/request/request.php | 415 +++++ phpBB/phpbb/request/type_cast_helper.php | 194 +++ phpBB/phpbb/request/type_cast_helper_interface.php | 63 + phpBB/phpbb/search/base.php | 330 ++++ phpBB/phpbb/search/fulltext_mysql.php | 948 ++++++++++ phpBB/phpbb/search/fulltext_native.php | 1821 ++++++++++++++++++++ phpBB/phpbb/search/fulltext_postgres.php | 963 +++++++++++ phpBB/phpbb/search/fulltext_sphinx.php | 917 ++++++++++ phpBB/phpbb/search/index.htm | 10 + phpBB/phpbb/search/sphinx/config.php | 288 ++++ phpBB/phpbb/search/sphinx/config_comment.php | 49 + phpBB/phpbb/search/sphinx/config_section.php | 162 ++ phpBB/phpbb/search/sphinx/config_variable.php | 80 + phpBB/phpbb/session.php | 1516 ++++++++++++++++ phpBB/phpbb/style/extension_path_provider.php | 137 ++ phpBB/phpbb/style/path_provider.php | 62 + phpBB/phpbb/style/path_provider_interface.php | 42 + phpBB/phpbb/style/resource_locator.php | 348 ++++ phpBB/phpbb/style/style.php | 241 +++ phpBB/phpbb/template/asset.php | 182 ++ phpBB/phpbb/template/context.php | 389 +++++ phpBB/phpbb/template/locator.php | 163 ++ phpBB/phpbb/template/template.php | 157 ++ phpBB/phpbb/template/twig/definition.php | 69 + phpBB/phpbb/template/twig/environment.php | 140 ++ phpBB/phpbb/template/twig/extension.php | 188 ++ phpBB/phpbb/template/twig/lexer.php | 300 ++++ phpBB/phpbb/template/twig/node/define.php | 58 + phpBB/phpbb/template/twig/node/event.php | 79 + .../twig/node/expression/binary/equalequal.php | 25 + .../twig/node/expression/binary/notequalequal.php | 25 + phpBB/phpbb/template/twig/node/include.php | 56 + phpBB/phpbb/template/twig/node/includeasset.php | 60 + phpBB/phpbb/template/twig/node/includecss.php | 30 + phpBB/phpbb/template/twig/node/includejs.php | 32 + phpBB/phpbb/template/twig/node/includephp.php | 91 + phpBB/phpbb/template/twig/node/php.php | 55 + phpBB/phpbb/template/twig/tokenparser/define.php | 66 + phpBB/phpbb/template/twig/tokenparser/event.php | 47 + phpBB/phpbb/template/twig/tokenparser/include.php | 46 + .../phpbb/template/twig/tokenparser/includecss.php | 38 + .../phpbb/template/twig/tokenparser/includejs.php | 47 + .../phpbb/template/twig/tokenparser/includephp.php | 56 + phpBB/phpbb/template/twig/tokenparser/php.php | 55 + phpBB/phpbb/template/twig/twig.php | 465 +++++ phpBB/phpbb/tree/interface.php | 122 ++ phpBB/phpbb/tree/nestedset.php | 850 +++++++++ phpBB/phpbb/tree/nestedset_forum.php | 46 + phpBB/phpbb/user.php | 857 +++++++++ phpBB/phpbb/user_loader.php | 231 +++ 497 files changed, 46798 insertions(+), 46803 deletions(-) delete mode 100644 phpBB/includes/auth/auth.php delete mode 100644 phpBB/includes/auth/index.htm delete mode 100644 phpBB/includes/auth/provider/apache.php delete mode 100644 phpBB/includes/auth/provider/base.php delete mode 100644 phpBB/includes/auth/provider/db.php delete mode 100644 phpBB/includes/auth/provider/index.htm delete mode 100644 phpBB/includes/auth/provider/interface.php delete mode 100644 phpBB/includes/auth/provider/ldap.php delete mode 100644 phpBB/includes/avatar/driver/driver.php delete mode 100644 phpBB/includes/avatar/driver/gravatar.php delete mode 100644 phpBB/includes/avatar/driver/interface.php delete mode 100644 phpBB/includes/avatar/driver/local.php delete mode 100644 phpBB/includes/avatar/driver/remote.php delete mode 100644 phpBB/includes/avatar/driver/upload.php delete mode 100644 phpBB/includes/avatar/manager.php delete mode 100644 phpBB/includes/cache/driver/apc.php delete mode 100644 phpBB/includes/cache/driver/base.php delete mode 100644 phpBB/includes/cache/driver/eaccelerator.php delete mode 100644 phpBB/includes/cache/driver/file.php delete mode 100644 phpBB/includes/cache/driver/interface.php delete mode 100644 phpBB/includes/cache/driver/memcache.php delete mode 100644 phpBB/includes/cache/driver/memory.php delete mode 100644 phpBB/includes/cache/driver/null.php delete mode 100644 phpBB/includes/cache/driver/redis.php delete mode 100644 phpBB/includes/cache/driver/wincache.php delete mode 100644 phpBB/includes/cache/driver/xcache.php delete mode 100644 phpBB/includes/cache/service.php delete mode 100644 phpBB/includes/class_loader.php delete mode 100644 phpBB/includes/config/config.php delete mode 100644 phpBB/includes/config/db.php delete mode 100644 phpBB/includes/config/db_text.php delete mode 100644 phpBB/includes/content_visibility.php delete mode 100644 phpBB/includes/controller/exception.php delete mode 100644 phpBB/includes/controller/helper.php delete mode 100644 phpBB/includes/controller/provider.php delete mode 100644 phpBB/includes/controller/resolver.php delete mode 100644 phpBB/includes/cron/manager.php delete mode 100644 phpBB/includes/cron/task/base.php delete mode 100644 phpBB/includes/cron/task/core/prune_all_forums.php delete mode 100644 phpBB/includes/cron/task/core/prune_forum.php delete mode 100644 phpBB/includes/cron/task/core/queue.php delete mode 100644 phpBB/includes/cron/task/core/tidy_cache.php delete mode 100644 phpBB/includes/cron/task/core/tidy_database.php delete mode 100644 phpBB/includes/cron/task/core/tidy_search.php delete mode 100644 phpBB/includes/cron/task/core/tidy_sessions.php delete mode 100644 phpBB/includes/cron/task/core/tidy_warnings.php delete mode 100644 phpBB/includes/cron/task/parametrized.php delete mode 100644 phpBB/includes/cron/task/task.php delete mode 100644 phpBB/includes/cron/task/wrapper.php delete mode 100644 phpBB/includes/datetime.php delete mode 100644 phpBB/includes/db/driver/driver.php delete mode 100644 phpBB/includes/db/driver/firebird.php delete mode 100644 phpBB/includes/db/driver/mssql.php delete mode 100644 phpBB/includes/db/driver/mssql_base.php delete mode 100644 phpBB/includes/db/driver/mssql_odbc.php delete mode 100644 phpBB/includes/db/driver/mssqlnative.php delete mode 100644 phpBB/includes/db/driver/mysql.php delete mode 100644 phpBB/includes/db/driver/mysql_base.php delete mode 100644 phpBB/includes/db/driver/mysqli.php delete mode 100644 phpBB/includes/db/driver/oracle.php delete mode 100644 phpBB/includes/db/driver/postgres.php delete mode 100644 phpBB/includes/db/driver/sqlite.php delete mode 100644 phpBB/includes/db/migration/data/30x/3_0_1.php delete mode 100644 phpBB/includes/db/migration/data/30x/3_0_10.php delete mode 100644 phpBB/includes/db/migration/data/30x/3_0_10_rc1.php delete mode 100644 phpBB/includes/db/migration/data/30x/3_0_10_rc2.php delete mode 100644 phpBB/includes/db/migration/data/30x/3_0_10_rc3.php delete mode 100644 phpBB/includes/db/migration/data/30x/3_0_11.php delete mode 100644 phpBB/includes/db/migration/data/30x/3_0_11_rc1.php delete mode 100644 phpBB/includes/db/migration/data/30x/3_0_11_rc2.php delete mode 100644 phpBB/includes/db/migration/data/30x/3_0_12_rc1.php delete mode 100644 phpBB/includes/db/migration/data/30x/3_0_1_rc1.php delete mode 100644 phpBB/includes/db/migration/data/30x/3_0_2.php delete mode 100644 phpBB/includes/db/migration/data/30x/3_0_2_rc1.php delete mode 100644 phpBB/includes/db/migration/data/30x/3_0_2_rc2.php delete mode 100644 phpBB/includes/db/migration/data/30x/3_0_3.php delete mode 100644 phpBB/includes/db/migration/data/30x/3_0_3_rc1.php delete mode 100644 phpBB/includes/db/migration/data/30x/3_0_4.php delete mode 100644 phpBB/includes/db/migration/data/30x/3_0_4_rc1.php delete mode 100644 phpBB/includes/db/migration/data/30x/3_0_5.php delete mode 100644 phpBB/includes/db/migration/data/30x/3_0_5_rc1.php delete mode 100644 phpBB/includes/db/migration/data/30x/3_0_5_rc1part2.php delete mode 100644 phpBB/includes/db/migration/data/30x/3_0_6.php delete mode 100644 phpBB/includes/db/migration/data/30x/3_0_6_rc1.php delete mode 100644 phpBB/includes/db/migration/data/30x/3_0_6_rc2.php delete mode 100644 phpBB/includes/db/migration/data/30x/3_0_6_rc3.php delete mode 100644 phpBB/includes/db/migration/data/30x/3_0_6_rc4.php delete mode 100644 phpBB/includes/db/migration/data/30x/3_0_7.php delete mode 100644 phpBB/includes/db/migration/data/30x/3_0_7_pl1.php delete mode 100644 phpBB/includes/db/migration/data/30x/3_0_7_rc1.php delete mode 100644 phpBB/includes/db/migration/data/30x/3_0_7_rc2.php delete mode 100644 phpBB/includes/db/migration/data/30x/3_0_8.php delete mode 100644 phpBB/includes/db/migration/data/30x/3_0_8_rc1.php delete mode 100644 phpBB/includes/db/migration/data/30x/3_0_9.php delete mode 100644 phpBB/includes/db/migration/data/30x/3_0_9_rc1.php delete mode 100644 phpBB/includes/db/migration/data/30x/3_0_9_rc2.php delete mode 100644 phpBB/includes/db/migration/data/30x/3_0_9_rc3.php delete mode 100644 phpBB/includes/db/migration/data/30x/3_0_9_rc4.php delete mode 100644 phpBB/includes/db/migration/data/30x/local_url_bbcode.php delete mode 100644 phpBB/includes/db/migration/data/310/avatars.php delete mode 100644 phpBB/includes/db/migration/data/310/boardindex.php delete mode 100644 phpBB/includes/db/migration/data/310/config_db_text.php delete mode 100644 phpBB/includes/db/migration/data/310/dev.php delete mode 100644 phpBB/includes/db/migration/data/310/extensions.php delete mode 100644 phpBB/includes/db/migration/data/310/forgot_password.php delete mode 100644 phpBB/includes/db/migration/data/310/jquery_update.php delete mode 100644 phpBB/includes/db/migration/data/310/notification_options_reconvert.php delete mode 100644 phpBB/includes/db/migration/data/310/notifications.php delete mode 100644 phpBB/includes/db/migration/data/310/notifications_schema_fix.php delete mode 100644 phpBB/includes/db/migration/data/310/reported_posts_display.php delete mode 100644 phpBB/includes/db/migration/data/310/signature_module_auth.php delete mode 100644 phpBB/includes/db/migration/data/310/softdelete_p1.php delete mode 100644 phpBB/includes/db/migration/data/310/softdelete_p2.php delete mode 100644 phpBB/includes/db/migration/data/310/style_update_p1.php delete mode 100644 phpBB/includes/db/migration/data/310/style_update_p2.php delete mode 100644 phpBB/includes/db/migration/data/310/teampage.php delete mode 100644 phpBB/includes/db/migration/data/310/timezone.php delete mode 100644 phpBB/includes/db/migration/data/310/timezone_p2.php delete mode 100644 phpBB/includes/db/migration/exception.php delete mode 100644 phpBB/includes/db/migration/migration.php delete mode 100644 phpBB/includes/db/migration/tool/config.php delete mode 100644 phpBB/includes/db/migration/tool/interface.php delete mode 100644 phpBB/includes/db/migration/tool/module.php delete mode 100644 phpBB/includes/db/migration/tool/permission.php delete mode 100644 phpBB/includes/db/migrator.php delete mode 100644 phpBB/includes/db/sql_insert_buffer.php delete mode 100644 phpBB/includes/di/extension/config.php delete mode 100644 phpBB/includes/di/extension/core.php delete mode 100644 phpBB/includes/di/extension/ext.php delete mode 100644 phpBB/includes/di/pass/collection_pass.php delete mode 100644 phpBB/includes/di/pass/kernel_pass.php delete mode 100644 phpBB/includes/di/service_collection.php delete mode 100644 phpBB/includes/error_collector.php delete mode 100644 phpBB/includes/event/data.php delete mode 100644 phpBB/includes/event/dispatcher.php delete mode 100644 phpBB/includes/event/extension_subscriber_loader.php delete mode 100644 phpBB/includes/event/kernel_exception_subscriber.php delete mode 100644 phpBB/includes/event/kernel_request_subscriber.php delete mode 100644 phpBB/includes/event/kernel_terminate_subscriber.php delete mode 100644 phpBB/includes/extension/base.php delete mode 100644 phpBB/includes/extension/exception.php delete mode 100644 phpBB/includes/extension/finder.php delete mode 100644 phpBB/includes/extension/interface.php delete mode 100644 phpBB/includes/extension/manager.php delete mode 100644 phpBB/includes/extension/metadata_manager.php delete mode 100644 phpBB/includes/extension/provider.php delete mode 100644 phpBB/includes/feed/base.php delete mode 100644 phpBB/includes/feed/factory.php delete mode 100644 phpBB/includes/feed/forum.php delete mode 100644 phpBB/includes/feed/forums.php delete mode 100644 phpBB/includes/feed/helper.php delete mode 100644 phpBB/includes/feed/news.php delete mode 100644 phpBB/includes/feed/overall.php delete mode 100644 phpBB/includes/feed/post_base.php delete mode 100644 phpBB/includes/feed/topic.php delete mode 100644 phpBB/includes/feed/topic_base.php delete mode 100644 phpBB/includes/feed/topics.php delete mode 100644 phpBB/includes/feed/topics_active.php delete mode 100644 phpBB/includes/filesystem.php delete mode 100644 phpBB/includes/groupposition/exception.php delete mode 100644 phpBB/includes/groupposition/interface.php delete mode 100644 phpBB/includes/groupposition/legend.php delete mode 100644 phpBB/includes/groupposition/teampage.php delete mode 100644 phpBB/includes/hook/finder.php delete mode 100644 phpBB/includes/json_response.php delete mode 100644 phpBB/includes/lock/db.php delete mode 100644 phpBB/includes/lock/flock.php delete mode 100644 phpBB/includes/log/interface.php delete mode 100644 phpBB/includes/log/log.php delete mode 100644 phpBB/includes/notification/exception.php delete mode 100644 phpBB/includes/notification/manager.php delete mode 100644 phpBB/includes/notification/method/base.php delete mode 100644 phpBB/includes/notification/method/email.php delete mode 100644 phpBB/includes/notification/method/interface.php delete mode 100644 phpBB/includes/notification/method/jabber.php delete mode 100644 phpBB/includes/notification/method/messenger_base.php delete mode 100644 phpBB/includes/notification/type/approve_post.php delete mode 100644 phpBB/includes/notification/type/approve_topic.php delete mode 100644 phpBB/includes/notification/type/base.php delete mode 100644 phpBB/includes/notification/type/bookmark.php delete mode 100644 phpBB/includes/notification/type/disapprove_post.php delete mode 100644 phpBB/includes/notification/type/disapprove_topic.php delete mode 100644 phpBB/includes/notification/type/interface.php delete mode 100644 phpBB/includes/notification/type/pm.php delete mode 100644 phpBB/includes/notification/type/post.php delete mode 100644 phpBB/includes/notification/type/post_in_queue.php delete mode 100644 phpBB/includes/notification/type/quote.php delete mode 100644 phpBB/includes/notification/type/report_pm.php delete mode 100644 phpBB/includes/notification/type/report_pm_closed.php delete mode 100644 phpBB/includes/notification/type/report_post.php delete mode 100644 phpBB/includes/notification/type/report_post_closed.php delete mode 100644 phpBB/includes/notification/type/topic.php delete mode 100644 phpBB/includes/notification/type/topic_in_queue.php delete mode 100644 phpBB/includes/php/ini.php delete mode 100644 phpBB/includes/request/deactivated_super_global.php delete mode 100644 phpBB/includes/request/interface.php delete mode 100644 phpBB/includes/request/request.php delete mode 100644 phpBB/includes/request/type_cast_helper.php delete mode 100644 phpBB/includes/request/type_cast_helper_interface.php delete mode 100644 phpBB/includes/search/base.php delete mode 100644 phpBB/includes/search/fulltext_mysql.php delete mode 100644 phpBB/includes/search/fulltext_native.php delete mode 100644 phpBB/includes/search/fulltext_postgres.php delete mode 100644 phpBB/includes/search/fulltext_sphinx.php delete mode 100644 phpBB/includes/search/index.htm delete mode 100644 phpBB/includes/search/sphinx/config.php delete mode 100644 phpBB/includes/search/sphinx/config_comment.php delete mode 100644 phpBB/includes/search/sphinx/config_section.php delete mode 100644 phpBB/includes/search/sphinx/config_variable.php delete mode 100644 phpBB/includes/session.php delete mode 100644 phpBB/includes/style/extension_path_provider.php delete mode 100644 phpBB/includes/style/path_provider.php delete mode 100644 phpBB/includes/style/path_provider_interface.php delete mode 100644 phpBB/includes/style/resource_locator.php delete mode 100644 phpBB/includes/style/style.php delete mode 100644 phpBB/includes/template/asset.php delete mode 100644 phpBB/includes/template/context.php delete mode 100644 phpBB/includes/template/locator.php delete mode 100644 phpBB/includes/template/template.php delete mode 100644 phpBB/includes/template/twig/definition.php delete mode 100644 phpBB/includes/template/twig/environment.php delete mode 100644 phpBB/includes/template/twig/extension.php delete mode 100644 phpBB/includes/template/twig/lexer.php delete mode 100644 phpBB/includes/template/twig/node/define.php delete mode 100644 phpBB/includes/template/twig/node/event.php delete mode 100644 phpBB/includes/template/twig/node/expression/binary/equalequal.php delete mode 100644 phpBB/includes/template/twig/node/expression/binary/notequalequal.php delete mode 100644 phpBB/includes/template/twig/node/include.php delete mode 100644 phpBB/includes/template/twig/node/includeasset.php delete mode 100644 phpBB/includes/template/twig/node/includecss.php delete mode 100644 phpBB/includes/template/twig/node/includejs.php delete mode 100644 phpBB/includes/template/twig/node/includephp.php delete mode 100644 phpBB/includes/template/twig/node/php.php delete mode 100644 phpBB/includes/template/twig/tokenparser/define.php delete mode 100644 phpBB/includes/template/twig/tokenparser/event.php delete mode 100644 phpBB/includes/template/twig/tokenparser/include.php delete mode 100644 phpBB/includes/template/twig/tokenparser/includecss.php delete mode 100644 phpBB/includes/template/twig/tokenparser/includejs.php delete mode 100644 phpBB/includes/template/twig/tokenparser/includephp.php delete mode 100644 phpBB/includes/template/twig/tokenparser/php.php delete mode 100644 phpBB/includes/template/twig/twig.php delete mode 100644 phpBB/includes/tree/interface.php delete mode 100644 phpBB/includes/tree/nestedset.php delete mode 100644 phpBB/includes/tree/nestedset_forum.php delete mode 100644 phpBB/includes/user.php delete mode 100644 phpBB/includes/user_loader.php create mode 100644 phpBB/phpbb/auth/auth.php create mode 100644 phpBB/phpbb/auth/index.htm create mode 100644 phpBB/phpbb/auth/provider/apache.php create mode 100644 phpBB/phpbb/auth/provider/base.php create mode 100644 phpBB/phpbb/auth/provider/db.php create mode 100644 phpBB/phpbb/auth/provider/index.htm create mode 100644 phpBB/phpbb/auth/provider/interface.php create mode 100644 phpBB/phpbb/auth/provider/ldap.php create mode 100644 phpBB/phpbb/avatar/driver/driver.php create mode 100644 phpBB/phpbb/avatar/driver/gravatar.php create mode 100644 phpBB/phpbb/avatar/driver/interface.php create mode 100644 phpBB/phpbb/avatar/driver/local.php create mode 100644 phpBB/phpbb/avatar/driver/remote.php create mode 100644 phpBB/phpbb/avatar/driver/upload.php create mode 100644 phpBB/phpbb/avatar/manager.php create mode 100644 phpBB/phpbb/cache/driver/apc.php create mode 100644 phpBB/phpbb/cache/driver/base.php create mode 100644 phpBB/phpbb/cache/driver/eaccelerator.php create mode 100644 phpBB/phpbb/cache/driver/file.php create mode 100644 phpBB/phpbb/cache/driver/interface.php create mode 100644 phpBB/phpbb/cache/driver/memcache.php create mode 100644 phpBB/phpbb/cache/driver/memory.php create mode 100644 phpBB/phpbb/cache/driver/null.php create mode 100644 phpBB/phpbb/cache/driver/redis.php create mode 100644 phpBB/phpbb/cache/driver/wincache.php create mode 100644 phpBB/phpbb/cache/driver/xcache.php create mode 100644 phpBB/phpbb/cache/service.php create mode 100644 phpBB/phpbb/class_loader.php create mode 100644 phpBB/phpbb/config/config.php create mode 100644 phpBB/phpbb/config/db.php create mode 100644 phpBB/phpbb/config/db_text.php create mode 100644 phpBB/phpbb/content_visibility.php create mode 100644 phpBB/phpbb/controller/exception.php create mode 100644 phpBB/phpbb/controller/helper.php create mode 100644 phpBB/phpbb/controller/provider.php create mode 100644 phpBB/phpbb/controller/resolver.php create mode 100644 phpBB/phpbb/cron/manager.php create mode 100644 phpBB/phpbb/cron/task/base.php create mode 100644 phpBB/phpbb/cron/task/core/prune_all_forums.php create mode 100644 phpBB/phpbb/cron/task/core/prune_forum.php create mode 100644 phpBB/phpbb/cron/task/core/queue.php create mode 100644 phpBB/phpbb/cron/task/core/tidy_cache.php create mode 100644 phpBB/phpbb/cron/task/core/tidy_database.php create mode 100644 phpBB/phpbb/cron/task/core/tidy_search.php create mode 100644 phpBB/phpbb/cron/task/core/tidy_sessions.php create mode 100644 phpBB/phpbb/cron/task/core/tidy_warnings.php create mode 100644 phpBB/phpbb/cron/task/parametrized.php create mode 100644 phpBB/phpbb/cron/task/task.php create mode 100644 phpBB/phpbb/cron/task/wrapper.php create mode 100644 phpBB/phpbb/datetime.php create mode 100644 phpBB/phpbb/db/driver/driver.php create mode 100644 phpBB/phpbb/db/driver/firebird.php create mode 100644 phpBB/phpbb/db/driver/mssql.php create mode 100644 phpBB/phpbb/db/driver/mssql_base.php create mode 100644 phpBB/phpbb/db/driver/mssql_odbc.php create mode 100644 phpBB/phpbb/db/driver/mssqlnative.php create mode 100644 phpBB/phpbb/db/driver/mysql.php create mode 100644 phpBB/phpbb/db/driver/mysql_base.php create mode 100644 phpBB/phpbb/db/driver/mysqli.php create mode 100644 phpBB/phpbb/db/driver/oracle.php create mode 100644 phpBB/phpbb/db/driver/postgres.php create mode 100644 phpBB/phpbb/db/driver/sqlite.php create mode 100644 phpBB/phpbb/db/migration/data/30x/3_0_1.php create mode 100644 phpBB/phpbb/db/migration/data/30x/3_0_10.php create mode 100644 phpBB/phpbb/db/migration/data/30x/3_0_10_rc1.php create mode 100644 phpBB/phpbb/db/migration/data/30x/3_0_10_rc2.php create mode 100644 phpBB/phpbb/db/migration/data/30x/3_0_10_rc3.php create mode 100644 phpBB/phpbb/db/migration/data/30x/3_0_11.php create mode 100644 phpBB/phpbb/db/migration/data/30x/3_0_11_rc1.php create mode 100644 phpBB/phpbb/db/migration/data/30x/3_0_11_rc2.php create mode 100644 phpBB/phpbb/db/migration/data/30x/3_0_12_rc1.php create mode 100644 phpBB/phpbb/db/migration/data/30x/3_0_1_rc1.php create mode 100644 phpBB/phpbb/db/migration/data/30x/3_0_2.php create mode 100644 phpBB/phpbb/db/migration/data/30x/3_0_2_rc1.php create mode 100644 phpBB/phpbb/db/migration/data/30x/3_0_2_rc2.php create mode 100644 phpBB/phpbb/db/migration/data/30x/3_0_3.php create mode 100644 phpBB/phpbb/db/migration/data/30x/3_0_3_rc1.php create mode 100644 phpBB/phpbb/db/migration/data/30x/3_0_4.php create mode 100644 phpBB/phpbb/db/migration/data/30x/3_0_4_rc1.php create mode 100644 phpBB/phpbb/db/migration/data/30x/3_0_5.php create mode 100644 phpBB/phpbb/db/migration/data/30x/3_0_5_rc1.php create mode 100644 phpBB/phpbb/db/migration/data/30x/3_0_5_rc1part2.php create mode 100644 phpBB/phpbb/db/migration/data/30x/3_0_6.php create mode 100644 phpBB/phpbb/db/migration/data/30x/3_0_6_rc1.php create mode 100644 phpBB/phpbb/db/migration/data/30x/3_0_6_rc2.php create mode 100644 phpBB/phpbb/db/migration/data/30x/3_0_6_rc3.php create mode 100644 phpBB/phpbb/db/migration/data/30x/3_0_6_rc4.php create mode 100644 phpBB/phpbb/db/migration/data/30x/3_0_7.php create mode 100644 phpBB/phpbb/db/migration/data/30x/3_0_7_pl1.php create mode 100644 phpBB/phpbb/db/migration/data/30x/3_0_7_rc1.php create mode 100644 phpBB/phpbb/db/migration/data/30x/3_0_7_rc2.php create mode 100644 phpBB/phpbb/db/migration/data/30x/3_0_8.php create mode 100644 phpBB/phpbb/db/migration/data/30x/3_0_8_rc1.php create mode 100644 phpBB/phpbb/db/migration/data/30x/3_0_9.php create mode 100644 phpBB/phpbb/db/migration/data/30x/3_0_9_rc1.php create mode 100644 phpBB/phpbb/db/migration/data/30x/3_0_9_rc2.php create mode 100644 phpBB/phpbb/db/migration/data/30x/3_0_9_rc3.php create mode 100644 phpBB/phpbb/db/migration/data/30x/3_0_9_rc4.php create mode 100644 phpBB/phpbb/db/migration/data/30x/local_url_bbcode.php create mode 100644 phpBB/phpbb/db/migration/data/310/avatars.php create mode 100644 phpBB/phpbb/db/migration/data/310/boardindex.php create mode 100644 phpBB/phpbb/db/migration/data/310/config_db_text.php create mode 100644 phpBB/phpbb/db/migration/data/310/dev.php create mode 100644 phpBB/phpbb/db/migration/data/310/extensions.php create mode 100644 phpBB/phpbb/db/migration/data/310/forgot_password.php create mode 100644 phpBB/phpbb/db/migration/data/310/jquery_update.php create mode 100644 phpBB/phpbb/db/migration/data/310/notification_options_reconvert.php create mode 100644 phpBB/phpbb/db/migration/data/310/notifications.php create mode 100644 phpBB/phpbb/db/migration/data/310/notifications_schema_fix.php create mode 100644 phpBB/phpbb/db/migration/data/310/reported_posts_display.php create mode 100644 phpBB/phpbb/db/migration/data/310/signature_module_auth.php create mode 100644 phpBB/phpbb/db/migration/data/310/softdelete_p1.php create mode 100644 phpBB/phpbb/db/migration/data/310/softdelete_p2.php create mode 100644 phpBB/phpbb/db/migration/data/310/style_update_p1.php create mode 100644 phpBB/phpbb/db/migration/data/310/style_update_p2.php create mode 100644 phpBB/phpbb/db/migration/data/310/teampage.php create mode 100644 phpBB/phpbb/db/migration/data/310/timezone.php create mode 100644 phpBB/phpbb/db/migration/data/310/timezone_p2.php create mode 100644 phpBB/phpbb/db/migration/exception.php create mode 100644 phpBB/phpbb/db/migration/migration.php create mode 100644 phpBB/phpbb/db/migration/tool/config.php create mode 100644 phpBB/phpbb/db/migration/tool/interface.php create mode 100644 phpBB/phpbb/db/migration/tool/module.php create mode 100644 phpBB/phpbb/db/migration/tool/permission.php create mode 100644 phpBB/phpbb/db/migrator.php create mode 100644 phpBB/phpbb/db/sql_insert_buffer.php create mode 100644 phpBB/phpbb/di/extension/config.php create mode 100644 phpBB/phpbb/di/extension/core.php create mode 100644 phpBB/phpbb/di/extension/ext.php create mode 100644 phpBB/phpbb/di/pass/collection_pass.php create mode 100644 phpBB/phpbb/di/pass/kernel_pass.php create mode 100644 phpBB/phpbb/di/service_collection.php create mode 100644 phpBB/phpbb/error_collector.php create mode 100644 phpBB/phpbb/event/data.php create mode 100644 phpBB/phpbb/event/dispatcher.php create mode 100644 phpBB/phpbb/event/extension_subscriber_loader.php create mode 100644 phpBB/phpbb/event/kernel_exception_subscriber.php create mode 100644 phpBB/phpbb/event/kernel_request_subscriber.php create mode 100644 phpBB/phpbb/event/kernel_terminate_subscriber.php create mode 100644 phpBB/phpbb/extension/base.php create mode 100644 phpBB/phpbb/extension/exception.php create mode 100644 phpBB/phpbb/extension/finder.php create mode 100644 phpBB/phpbb/extension/interface.php create mode 100644 phpBB/phpbb/extension/manager.php create mode 100644 phpBB/phpbb/extension/metadata_manager.php create mode 100644 phpBB/phpbb/extension/provider.php create mode 100644 phpBB/phpbb/feed/base.php create mode 100644 phpBB/phpbb/feed/factory.php create mode 100644 phpBB/phpbb/feed/forum.php create mode 100644 phpBB/phpbb/feed/forums.php create mode 100644 phpBB/phpbb/feed/helper.php create mode 100644 phpBB/phpbb/feed/news.php create mode 100644 phpBB/phpbb/feed/overall.php create mode 100644 phpBB/phpbb/feed/post_base.php create mode 100644 phpBB/phpbb/feed/topic.php create mode 100644 phpBB/phpbb/feed/topic_base.php create mode 100644 phpBB/phpbb/feed/topics.php create mode 100644 phpBB/phpbb/feed/topics_active.php create mode 100644 phpBB/phpbb/filesystem.php create mode 100644 phpBB/phpbb/groupposition/exception.php create mode 100644 phpBB/phpbb/groupposition/interface.php create mode 100644 phpBB/phpbb/groupposition/legend.php create mode 100644 phpBB/phpbb/groupposition/teampage.php create mode 100644 phpBB/phpbb/hook/finder.php create mode 100644 phpBB/phpbb/json_response.php create mode 100644 phpBB/phpbb/lock/db.php create mode 100644 phpBB/phpbb/lock/flock.php create mode 100644 phpBB/phpbb/log/interface.php create mode 100644 phpBB/phpbb/log/log.php create mode 100644 phpBB/phpbb/notification/exception.php create mode 100644 phpBB/phpbb/notification/manager.php create mode 100644 phpBB/phpbb/notification/method/base.php create mode 100644 phpBB/phpbb/notification/method/email.php create mode 100644 phpBB/phpbb/notification/method/interface.php create mode 100644 phpBB/phpbb/notification/method/jabber.php create mode 100644 phpBB/phpbb/notification/method/messenger_base.php create mode 100644 phpBB/phpbb/notification/type/approve_post.php create mode 100644 phpBB/phpbb/notification/type/approve_topic.php create mode 100644 phpBB/phpbb/notification/type/base.php create mode 100644 phpBB/phpbb/notification/type/bookmark.php create mode 100644 phpBB/phpbb/notification/type/disapprove_post.php create mode 100644 phpBB/phpbb/notification/type/disapprove_topic.php create mode 100644 phpBB/phpbb/notification/type/interface.php create mode 100644 phpBB/phpbb/notification/type/pm.php create mode 100644 phpBB/phpbb/notification/type/post.php create mode 100644 phpBB/phpbb/notification/type/post_in_queue.php create mode 100644 phpBB/phpbb/notification/type/quote.php create mode 100644 phpBB/phpbb/notification/type/report_pm.php create mode 100644 phpBB/phpbb/notification/type/report_pm_closed.php create mode 100644 phpBB/phpbb/notification/type/report_post.php create mode 100644 phpBB/phpbb/notification/type/report_post_closed.php create mode 100644 phpBB/phpbb/notification/type/topic.php create mode 100644 phpBB/phpbb/notification/type/topic_in_queue.php create mode 100644 phpBB/phpbb/php/ini.php create mode 100644 phpBB/phpbb/request/deactivated_super_global.php create mode 100644 phpBB/phpbb/request/interface.php create mode 100644 phpBB/phpbb/request/request.php create mode 100644 phpBB/phpbb/request/type_cast_helper.php create mode 100644 phpBB/phpbb/request/type_cast_helper_interface.php create mode 100644 phpBB/phpbb/search/base.php create mode 100644 phpBB/phpbb/search/fulltext_mysql.php create mode 100644 phpBB/phpbb/search/fulltext_native.php create mode 100644 phpBB/phpbb/search/fulltext_postgres.php create mode 100644 phpBB/phpbb/search/fulltext_sphinx.php create mode 100644 phpBB/phpbb/search/index.htm create mode 100644 phpBB/phpbb/search/sphinx/config.php create mode 100644 phpBB/phpbb/search/sphinx/config_comment.php create mode 100644 phpBB/phpbb/search/sphinx/config_section.php create mode 100644 phpBB/phpbb/search/sphinx/config_variable.php create mode 100644 phpBB/phpbb/session.php create mode 100644 phpBB/phpbb/style/extension_path_provider.php create mode 100644 phpBB/phpbb/style/path_provider.php create mode 100644 phpBB/phpbb/style/path_provider_interface.php create mode 100644 phpBB/phpbb/style/resource_locator.php create mode 100644 phpBB/phpbb/style/style.php create mode 100644 phpBB/phpbb/template/asset.php create mode 100644 phpBB/phpbb/template/context.php create mode 100644 phpBB/phpbb/template/locator.php create mode 100644 phpBB/phpbb/template/template.php create mode 100644 phpBB/phpbb/template/twig/definition.php create mode 100644 phpBB/phpbb/template/twig/environment.php create mode 100644 phpBB/phpbb/template/twig/extension.php create mode 100644 phpBB/phpbb/template/twig/lexer.php create mode 100644 phpBB/phpbb/template/twig/node/define.php create mode 100644 phpBB/phpbb/template/twig/node/event.php create mode 100644 phpBB/phpbb/template/twig/node/expression/binary/equalequal.php create mode 100644 phpBB/phpbb/template/twig/node/expression/binary/notequalequal.php create mode 100644 phpBB/phpbb/template/twig/node/include.php create mode 100644 phpBB/phpbb/template/twig/node/includeasset.php create mode 100644 phpBB/phpbb/template/twig/node/includecss.php create mode 100644 phpBB/phpbb/template/twig/node/includejs.php create mode 100644 phpBB/phpbb/template/twig/node/includephp.php create mode 100644 phpBB/phpbb/template/twig/node/php.php create mode 100644 phpBB/phpbb/template/twig/tokenparser/define.php create mode 100644 phpBB/phpbb/template/twig/tokenparser/event.php create mode 100644 phpBB/phpbb/template/twig/tokenparser/include.php create mode 100644 phpBB/phpbb/template/twig/tokenparser/includecss.php create mode 100644 phpBB/phpbb/template/twig/tokenparser/includejs.php create mode 100644 phpBB/phpbb/template/twig/tokenparser/includephp.php create mode 100644 phpBB/phpbb/template/twig/tokenparser/php.php create mode 100644 phpBB/phpbb/template/twig/twig.php create mode 100644 phpBB/phpbb/tree/interface.php create mode 100644 phpBB/phpbb/tree/nestedset.php create mode 100644 phpBB/phpbb/tree/nestedset_forum.php create mode 100644 phpBB/phpbb/user.php create mode 100644 phpBB/phpbb/user_loader.php (limited to 'phpBB') diff --git a/phpBB/common.php b/phpBB/common.php index f6f109c3de..962a1f951f 100644 --- a/phpBB/common.php +++ b/phpBB/common.php @@ -46,7 +46,7 @@ if (!defined('PHPBB_INSTALLED')) $script_path = preg_replace('#[\\\\/]{2,}#', '/', $script_path); // Eliminate . and .. from the path - require($phpbb_root_path . 'includes/filesystem.' . $phpEx); + require($phpbb_root_path . 'phpbb/filesystem.' . $phpEx); $phpbb_filesystem = new phpbb_filesystem(); $script_path = $phpbb_filesystem->clean_path($script_path); @@ -71,7 +71,7 @@ $phpbb_adm_relative_path = (isset($phpbb_adm_relative_path)) ? $phpbb_adm_relati $phpbb_admin_path = (defined('PHPBB_ADMIN_PATH')) ? PHPBB_ADMIN_PATH : $phpbb_root_path . $phpbb_adm_relative_path; // Include files -require($phpbb_root_path . 'includes/class_loader.' . $phpEx); +require($phpbb_root_path . 'phpbb/class_loader.' . $phpEx); require($phpbb_root_path . 'includes/functions.' . $phpEx); require($phpbb_root_path . 'includes/functions_content.' . $phpEx); @@ -85,7 +85,7 @@ require($phpbb_root_path . 'includes/utf/utf_tools.' . $phpEx); set_error_handler(defined('PHPBB_MSG_HANDLER') ? PHPBB_MSG_HANDLER : 'msg_handler'); // Setup class loader first -$phpbb_class_loader = new phpbb_class_loader('phpbb_', "{$phpbb_root_path}includes/", $phpEx); +$phpbb_class_loader = new phpbb_class_loader('phpbb_', "{$phpbb_root_path}phpbb/", $phpEx); $phpbb_class_loader->register(); $phpbb_class_loader_ext = new phpbb_class_loader('phpbb_ext_', "{$phpbb_root_path}ext/", $phpEx); $phpbb_class_loader_ext->register(); diff --git a/phpBB/includes/acp/acp_search.php b/phpBB/includes/acp/acp_search.php index 6618e2c3f9..11a2511aee 100644 --- a/phpBB/includes/acp/acp_search.php +++ b/phpBB/includes/acp/acp_search.php @@ -560,7 +560,7 @@ class acp_search return $finder ->extension_suffix('_backend') ->extension_directory('/search') - ->core_path('includes/search/') + ->core_path('phpbb/search/') ->get_classes(); } diff --git a/phpBB/includes/auth/auth.php b/phpBB/includes/auth/auth.php deleted file mode 100644 index 279959974d..0000000000 --- a/phpBB/includes/auth/auth.php +++ /dev/null @@ -1,1080 +0,0 @@ -acl = $this->cache = $this->acl_options = array(); - $this->acl_forum_ids = false; - - if (($this->acl_options = $cache->get('_acl_options')) === false) - { - $sql = 'SELECT auth_option_id, auth_option, is_global, is_local - FROM ' . ACL_OPTIONS_TABLE . ' - ORDER BY auth_option_id'; - $result = $db->sql_query($sql); - - $global = $local = 0; - $this->acl_options = array(); - while ($row = $db->sql_fetchrow($result)) - { - if ($row['is_global']) - { - $this->acl_options['global'][$row['auth_option']] = $global++; - } - - if ($row['is_local']) - { - $this->acl_options['local'][$row['auth_option']] = $local++; - } - - $this->acl_options['id'][$row['auth_option']] = (int) $row['auth_option_id']; - $this->acl_options['option'][(int) $row['auth_option_id']] = $row['auth_option']; - } - $db->sql_freeresult($result); - - $cache->put('_acl_options', $this->acl_options); - } - - if (!trim($userdata['user_permissions'])) - { - $this->acl_cache($userdata); - } - - // Fill ACL array - $this->_fill_acl($userdata['user_permissions']); - - // Verify bitstring length with options provided... - $renew = false; - $global_length = sizeof($this->acl_options['global']); - $local_length = sizeof($this->acl_options['local']); - - // Specify comparing length (bitstring is padded to 31 bits) - $global_length = ($global_length % 31) ? ($global_length - ($global_length % 31) + 31) : $global_length; - $local_length = ($local_length % 31) ? ($local_length - ($local_length % 31) + 31) : $local_length; - - // You thought we are finished now? Noooo... now compare them. - foreach ($this->acl as $forum_id => $bitstring) - { - if (($forum_id && strlen($bitstring) != $local_length) || (!$forum_id && strlen($bitstring) != $global_length)) - { - $renew = true; - break; - } - } - - // If a bitstring within the list does not match the options, we have a user with incorrect permissions set and need to renew them - if ($renew) - { - $this->acl_cache($userdata); - $this->_fill_acl($userdata['user_permissions']); - } - - return; - } - - /** - * 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 - */ - function _fill_acl($user_permissions) - { - $seq_cache = array(); - $this->acl = array(); - $user_permissions = explode("\n", $user_permissions); - - foreach ($user_permissions as $f => $seq) - { - if ($seq) - { - $i = 0; - - if (!isset($this->acl[$f])) - { - $this->acl[$f] = ''; - } - - while ($subseq = substr($seq, $i, 6)) - { - if (isset($seq_cache[$subseq])) - { - $converted = $seq_cache[$subseq]; - } - else - { - $converted = $seq_cache[$subseq] = str_pad(base_convert($subseq, 36, 2), 31, 0, STR_PAD_LEFT); - } - - // We put the original bitstring into the acl array - $this->acl[$f] .= $converted; - $i += 6; - } - } - } - } - - /** - * Look up an option - * if the option is prefixed with !, then the result becomes negated - * - * If a forum id is specified the local option will be combined with a global option if one exist. - * If a forum id is not specified, only the global option will be checked. - */ - function acl_get($opt, $f = 0) - { - $negate = false; - - if (strpos($opt, '!') === 0) - { - $negate = true; - $opt = substr($opt, 1); - } - - if (!isset($this->cache[$f][$opt])) - { - // We combine the global/local option with an OR because some options are global and local. - // If the user has the global permission the local one is true too and vice versa - $this->cache[$f][$opt] = false; - - // Is this option a global permission setting? - if (isset($this->acl_options['global'][$opt])) - { - if (isset($this->acl[0])) - { - $this->cache[$f][$opt] = $this->acl[0][$this->acl_options['global'][$opt]]; - } - } - - // Is this option a local permission setting? - // But if we check for a global option only, we won't combine the options... - if ($f != 0 && isset($this->acl_options['local'][$opt])) - { - if (isset($this->acl[$f]) && isset($this->acl[$f][$this->acl_options['local'][$opt]])) - { - $this->cache[$f][$opt] |= $this->acl[$f][$this->acl_options['local'][$opt]]; - } - } - } - - // Founder always has all global options set to true... - return ($negate) ? !$this->cache[$f][$opt] : $this->cache[$f][$opt]; - } - - /** - * Get forums with the specified permission setting - * 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 - */ - function acl_getf($opt, $clean = false) - { - $acl_f = array(); - $negate = false; - - if (strpos($opt, '!') === 0) - { - $negate = true; - $opt = substr($opt, 1); - } - - // If we retrieve a list of forums not having permissions in, we need to get every forum_id - if ($negate) - { - if ($this->acl_forum_ids === false) - { - global $db; - - $sql = 'SELECT forum_id - FROM ' . FORUMS_TABLE; - - if (sizeof($this->acl)) - { - $sql .= ' WHERE ' . $db->sql_in_set('forum_id', array_keys($this->acl), true); - } - $result = $db->sql_query($sql); - - $this->acl_forum_ids = array(); - while ($row = $db->sql_fetchrow($result)) - { - $this->acl_forum_ids[] = $row['forum_id']; - } - $db->sql_freeresult($result); - } - } - - if (isset($this->acl_options['local'][$opt])) - { - foreach ($this->acl as $f => $bitstring) - { - // Skip global settings - if (!$f) - { - continue; - } - - $allowed = (!isset($this->cache[$f][$opt])) ? $this->acl_get($opt, $f) : $this->cache[$f][$opt]; - - if (!$clean) - { - $acl_f[$f][$opt] = ($negate) ? !$allowed : $allowed; - } - else - { - if (($negate && !$allowed) || (!$negate && $allowed)) - { - $acl_f[$f][$opt] = 1; - } - } - } - } - - // If we get forum_ids not having this permission, we need to fill the remaining parts - if ($negate && sizeof($this->acl_forum_ids)) - { - foreach ($this->acl_forum_ids as $f) - { - $acl_f[$f][$opt] = 1; - } - } - - return $acl_f; - } - - /** - * Get local permission state for any forum. - * - * Returns true if user has the permission in one or more forums, false if in no forum. - * If global option is checked it returns the global state (same as acl_get($opt)) - * Local option has precedence... - */ - function acl_getf_global($opt) - { - if (is_array($opt)) - { - // evaluates to true as soon as acl_getf_global is true for one option - foreach ($opt as $check_option) - { - if ($this->acl_getf_global($check_option)) - { - return true; - } - } - - return false; - } - - if (isset($this->acl_options['local'][$opt])) - { - foreach ($this->acl as $f => $bitstring) - { - // Skip global settings - if (!$f) - { - continue; - } - - // as soon as the user has any permission we're done so return true - if ((!isset($this->cache[$f][$opt])) ? $this->acl_get($opt, $f) : $this->cache[$f][$opt]) - { - return true; - } - } - } - else if (isset($this->acl_options['global'][$opt])) - { - return $this->acl_get($opt); - } - - return false; - } - - /** - * Get permission settings (more than one) - */ - function acl_gets() - { - $args = func_get_args(); - $f = array_pop($args); - - if (!is_numeric($f)) - { - $args[] = $f; - $f = 0; - } - - // alternate syntax: acl_gets(array('m_', 'a_'), $forum_id) - if (is_array($args[0])) - { - $args = $args[0]; - } - - $acl = 0; - foreach ($args as $opt) - { - $acl |= $this->acl_get($opt, $f); - } - - return $acl; - } - - /** - * Get permission listing based on user_id/options/forum_ids - * - * Be careful when using this function with permissions a_, m_, u_ and f_ ! - * It may not work correctly. When a user group grants an a_* permission, - * e.g. a_foo, but the user's a_foo permission is set to "Never", then - * the user does not in fact have the a_ permission. - * But the user will still be listed as having the a_ permission. - * - * For more information see: http://tracker.phpbb.com/browse/PHPBB3-10252 - */ - function acl_get_list($user_id = false, $opts = false, $forum_id = false) - { - if ($user_id !== false && !is_array($user_id) && $opts === false && $forum_id === false) - { - $hold_ary = array($user_id => $this->acl_raw_data_single_user($user_id)); - } - else - { - $hold_ary = $this->acl_raw_data($user_id, $opts, $forum_id); - } - - $auth_ary = array(); - foreach ($hold_ary as $user_id => $forum_ary) - { - foreach ($forum_ary as $forum_id => $auth_option_ary) - { - foreach ($auth_option_ary as $auth_option => $auth_setting) - { - if ($auth_setting) - { - $auth_ary[$forum_id][$auth_option][] = $user_id; - } - } - } - } - - return $auth_ary; - } - - /** - * Cache data to user_permissions row - */ - function acl_cache(&$userdata) - { - global $db; - - // Empty user_permissions - $userdata['user_permissions'] = ''; - - $hold_ary = $this->acl_raw_data_single_user($userdata['user_id']); - - // Key 0 in $hold_ary are global options, all others are forum_ids - - // If this user is founder we're going to force fill the admin options ... - if ($userdata['user_type'] == USER_FOUNDER) - { - foreach ($this->acl_options['global'] as $opt => $id) - { - if (strpos($opt, 'a_') === 0) - { - $hold_ary[0][$this->acl_options['id'][$opt]] = ACL_YES; - } - } - } - - $hold_str = $this->build_bitstring($hold_ary); - - if ($hold_str) - { - $userdata['user_permissions'] = $hold_str; - - $sql = 'UPDATE ' . USERS_TABLE . " - SET user_permissions = '" . $db->sql_escape($userdata['user_permissions']) . "', - user_perm_from = 0 - WHERE user_id = " . $userdata['user_id']; - $db->sql_query($sql); - } - - return; - } - - /** - * Build bitstring from permission set - */ - function build_bitstring(&$hold_ary) - { - $hold_str = ''; - - if (sizeof($hold_ary)) - { - ksort($hold_ary); - - $last_f = 0; - - foreach ($hold_ary as $f => $auth_ary) - { - $ary_key = (!$f) ? 'global' : 'local'; - - $bitstring = array(); - foreach ($this->acl_options[$ary_key] as $opt => $id) - { - if (isset($auth_ary[$this->acl_options['id'][$opt]])) - { - $bitstring[$id] = $auth_ary[$this->acl_options['id'][$opt]]; - - $option_key = substr($opt, 0, strpos($opt, '_') + 1); - - // If one option is allowed, the global permission for this option has to be allowed too - // example: if the user has the a_ permission this means he has one or more a_* permissions - if ($auth_ary[$this->acl_options['id'][$opt]] == ACL_YES && (!isset($bitstring[$this->acl_options[$ary_key][$option_key]]) || $bitstring[$this->acl_options[$ary_key][$option_key]] == ACL_NEVER)) - { - $bitstring[$this->acl_options[$ary_key][$option_key]] = ACL_YES; - } - } - else - { - $bitstring[$id] = ACL_NEVER; - } - } - - // Now this bitstring defines the permission setting for the current forum $f (or global setting) - $bitstring = implode('', $bitstring); - - // The line number indicates the id, therefore we have to add empty lines for those ids not present - $hold_str .= str_repeat("\n", $f - $last_f); - - // Convert bitstring for storage - we do not use binary/bytes because PHP's string functions are not fully binary safe - for ($i = 0, $bit_length = strlen($bitstring); $i < $bit_length; $i += 31) - { - $hold_str .= str_pad(base_convert(str_pad(substr($bitstring, $i, 31), 31, 0, STR_PAD_RIGHT), 2, 36), 6, 0, STR_PAD_LEFT); - } - - $last_f = $f; - } - unset($bitstring); - - $hold_str = rtrim($hold_str); - } - - return $hold_str; - } - - /** - * Clear one or all users cached permission settings - */ - function acl_clear_prefetch($user_id = false) - { - global $db, $cache; - - // Rebuild options cache - $cache->destroy('_role_cache'); - - $sql = 'SELECT * - FROM ' . ACL_ROLES_DATA_TABLE . ' - ORDER BY role_id ASC'; - $result = $db->sql_query($sql); - - $this->role_cache = array(); - while ($row = $db->sql_fetchrow($result)) - { - $this->role_cache[$row['role_id']][$row['auth_option_id']] = (int) $row['auth_setting']; - } - $db->sql_freeresult($result); - - foreach ($this->role_cache as $role_id => $role_options) - { - $this->role_cache[$role_id] = serialize($role_options); - } - - $cache->put('_role_cache', $this->role_cache); - - // Now empty user permissions - $where_sql = ''; - - if ($user_id !== false) - { - $user_id = (!is_array($user_id)) ? $user_id = array((int) $user_id) : array_map('intval', $user_id); - $where_sql = ' WHERE ' . $db->sql_in_set('user_id', $user_id); - } - - $sql = 'UPDATE ' . USERS_TABLE . " - SET user_permissions = '', - user_perm_from = 0 - $where_sql"; - $db->sql_query($sql); - - return; - } - - /** - * Get assigned roles - */ - function acl_role_data($user_type, $role_type, $ug_id = false, $forum_id = false) - { - global $db; - - $roles = array(); - - $sql_id = ($user_type == 'user') ? 'user_id' : 'group_id'; - - $sql_ug = ($ug_id !== false) ? ((!is_array($ug_id)) ? "AND a.$sql_id = $ug_id" : 'AND ' . $db->sql_in_set("a.$sql_id", $ug_id)) : ''; - $sql_forum = ($forum_id !== false) ? ((!is_array($forum_id)) ? "AND a.forum_id = $forum_id" : 'AND ' . $db->sql_in_set('a.forum_id', $forum_id)) : ''; - - // Grab assigned roles... - $sql = 'SELECT a.auth_role_id, a.' . $sql_id . ', a.forum_id - FROM ' . (($user_type == 'user') ? ACL_USERS_TABLE : ACL_GROUPS_TABLE) . ' a, ' . ACL_ROLES_TABLE . " r - WHERE a.auth_role_id = r.role_id - AND r.role_type = '" . $db->sql_escape($role_type) . "' - $sql_ug - $sql_forum - ORDER BY r.role_order ASC"; - $result = $db->sql_query($sql); - - while ($row = $db->sql_fetchrow($result)) - { - $roles[$row[$sql_id]][$row['forum_id']] = $row['auth_role_id']; - } - $db->sql_freeresult($result); - - return $roles; - } - - /** - * Get raw acl data based on user/option/forum - */ - function acl_raw_data($user_id = false, $opts = false, $forum_id = false) - { - global $db; - - $sql_user = ($user_id !== false) ? ((!is_array($user_id)) ? 'user_id = ' . (int) $user_id : $db->sql_in_set('user_id', array_map('intval', $user_id))) : ''; - $sql_forum = ($forum_id !== false) ? ((!is_array($forum_id)) ? 'AND a.forum_id = ' . (int) $forum_id : 'AND ' . $db->sql_in_set('a.forum_id', array_map('intval', $forum_id))) : ''; - - $sql_opts = $sql_opts_select = $sql_opts_from = ''; - $hold_ary = array(); - - if ($opts !== false) - { - $sql_opts_select = ', ao.auth_option'; - $sql_opts_from = ', ' . ACL_OPTIONS_TABLE . ' ao'; - $this->build_auth_option_statement('ao.auth_option', $opts, $sql_opts); - } - - $sql_ary = array(); - - // Grab non-role settings - user-specific - $sql_ary[] = 'SELECT a.user_id, a.forum_id, a.auth_setting, a.auth_option_id' . $sql_opts_select . ' - FROM ' . ACL_USERS_TABLE . ' a' . $sql_opts_from . ' - WHERE a.auth_role_id = 0 ' . - (($sql_opts_from) ? 'AND a.auth_option_id = ao.auth_option_id ' : '') . - (($sql_user) ? 'AND a.' . $sql_user : '') . " - $sql_forum - $sql_opts"; - - // Now the role settings - user-specific - $sql_ary[] = 'SELECT a.user_id, a.forum_id, r.auth_option_id, r.auth_setting, r.auth_option_id' . $sql_opts_select . ' - FROM ' . ACL_USERS_TABLE . ' a, ' . ACL_ROLES_DATA_TABLE . ' r' . $sql_opts_from . ' - WHERE a.auth_role_id = r.role_id ' . - (($sql_opts_from) ? 'AND r.auth_option_id = ao.auth_option_id ' : '') . - (($sql_user) ? 'AND a.' . $sql_user : '') . " - $sql_forum - $sql_opts"; - - foreach ($sql_ary as $sql) - { - $result = $db->sql_query($sql); - - while ($row = $db->sql_fetchrow($result)) - { - $option = ($sql_opts_select) ? $row['auth_option'] : $this->acl_options['option'][$row['auth_option_id']]; - $hold_ary[$row['user_id']][$row['forum_id']][$option] = $row['auth_setting']; - } - $db->sql_freeresult($result); - } - - $sql_ary = array(); - - // Now grab group settings - non-role specific... - $sql_ary[] = 'SELECT ug.user_id, a.forum_id, a.auth_setting, a.auth_option_id' . $sql_opts_select . ' - FROM ' . ACL_GROUPS_TABLE . ' a, ' . USER_GROUP_TABLE . ' ug, ' . GROUPS_TABLE . ' g' . $sql_opts_from . ' - WHERE a.auth_role_id = 0 ' . - (($sql_opts_from) ? 'AND a.auth_option_id = ao.auth_option_id ' : '') . ' - AND a.group_id = ug.group_id - AND g.group_id = ug.group_id - AND ug.user_pending = 0 - AND NOT (ug.group_leader = 1 AND g.group_skip_auth = 1) - ' . (($sql_user) ? 'AND ug.' . $sql_user : '') . " - $sql_forum - $sql_opts"; - - // Now grab group settings - role specific... - $sql_ary[] = 'SELECT ug.user_id, a.forum_id, r.auth_setting, r.auth_option_id' . $sql_opts_select . ' - FROM ' . ACL_GROUPS_TABLE . ' a, ' . USER_GROUP_TABLE . ' ug, ' . GROUPS_TABLE . ' g, ' . ACL_ROLES_DATA_TABLE . ' r' . $sql_opts_from . ' - WHERE a.auth_role_id = r.role_id ' . - (($sql_opts_from) ? 'AND r.auth_option_id = ao.auth_option_id ' : '') . ' - AND a.group_id = ug.group_id - AND g.group_id = ug.group_id - AND ug.user_pending = 0 - AND NOT (ug.group_leader = 1 AND g.group_skip_auth = 1) - ' . (($sql_user) ? 'AND ug.' . $sql_user : '') . " - $sql_forum - $sql_opts"; - - foreach ($sql_ary as $sql) - { - $result = $db->sql_query($sql); - - while ($row = $db->sql_fetchrow($result)) - { - $option = ($sql_opts_select) ? $row['auth_option'] : $this->acl_options['option'][$row['auth_option_id']]; - - if (!isset($hold_ary[$row['user_id']][$row['forum_id']][$option]) || (isset($hold_ary[$row['user_id']][$row['forum_id']][$option]) && $hold_ary[$row['user_id']][$row['forum_id']][$option] != ACL_NEVER)) - { - $hold_ary[$row['user_id']][$row['forum_id']][$option] = $row['auth_setting']; - - // If we detect ACL_NEVER, we will unset the flag option (within building the bitstring it is correctly set again) - if ($row['auth_setting'] == ACL_NEVER) - { - $flag = substr($option, 0, strpos($option, '_') + 1); - - if (isset($hold_ary[$row['user_id']][$row['forum_id']][$flag]) && $hold_ary[$row['user_id']][$row['forum_id']][$flag] == ACL_YES) - { - unset($hold_ary[$row['user_id']][$row['forum_id']][$flag]); - -/* if (in_array(ACL_YES, $hold_ary[$row['user_id']][$row['forum_id']])) - { - $hold_ary[$row['user_id']][$row['forum_id']][$flag] = ACL_YES; - } -*/ - } - } - } - } - $db->sql_freeresult($result); - } - - return $hold_ary; - } - - /** - * Get raw user based permission settings - */ - function acl_user_raw_data($user_id = false, $opts = false, $forum_id = false) - { - global $db; - - $sql_user = ($user_id !== false) ? ((!is_array($user_id)) ? 'user_id = ' . (int) $user_id : $db->sql_in_set('user_id', array_map('intval', $user_id))) : ''; - $sql_forum = ($forum_id !== false) ? ((!is_array($forum_id)) ? 'AND a.forum_id = ' . (int) $forum_id : 'AND ' . $db->sql_in_set('a.forum_id', array_map('intval', $forum_id))) : ''; - - $sql_opts = ''; - $hold_ary = $sql_ary = array(); - - if ($opts !== false) - { - $this->build_auth_option_statement('ao.auth_option', $opts, $sql_opts); - } - - // Grab user settings - non-role specific... - $sql_ary[] = 'SELECT a.user_id, a.forum_id, a.auth_setting, a.auth_option_id, ao.auth_option - FROM ' . ACL_USERS_TABLE . ' a, ' . ACL_OPTIONS_TABLE . ' ao - WHERE a.auth_role_id = 0 - AND a.auth_option_id = ao.auth_option_id ' . - (($sql_user) ? 'AND a.' . $sql_user : '') . " - $sql_forum - $sql_opts - ORDER BY a.forum_id, ao.auth_option"; - - // Now the role settings - user-specific - $sql_ary[] = 'SELECT a.user_id, a.forum_id, r.auth_option_id, r.auth_setting, r.auth_option_id, ao.auth_option - FROM ' . ACL_USERS_TABLE . ' a, ' . ACL_ROLES_DATA_TABLE . ' r, ' . ACL_OPTIONS_TABLE . ' ao - WHERE a.auth_role_id = r.role_id - AND r.auth_option_id = ao.auth_option_id ' . - (($sql_user) ? 'AND a.' . $sql_user : '') . " - $sql_forum - $sql_opts - ORDER BY a.forum_id, ao.auth_option"; - - foreach ($sql_ary as $sql) - { - $result = $db->sql_query($sql); - - while ($row = $db->sql_fetchrow($result)) - { - $hold_ary[$row['user_id']][$row['forum_id']][$row['auth_option']] = $row['auth_setting']; - } - $db->sql_freeresult($result); - } - - return $hold_ary; - } - - /** - * Get raw group based permission settings - */ - function acl_group_raw_data($group_id = false, $opts = false, $forum_id = false) - { - global $db; - - $sql_group = ($group_id !== false) ? ((!is_array($group_id)) ? 'group_id = ' . (int) $group_id : $db->sql_in_set('group_id', array_map('intval', $group_id))) : ''; - $sql_forum = ($forum_id !== false) ? ((!is_array($forum_id)) ? 'AND a.forum_id = ' . (int) $forum_id : 'AND ' . $db->sql_in_set('a.forum_id', array_map('intval', $forum_id))) : ''; - - $sql_opts = ''; - $hold_ary = $sql_ary = array(); - - if ($opts !== false) - { - $this->build_auth_option_statement('ao.auth_option', $opts, $sql_opts); - } - - // Grab group settings - non-role specific... - $sql_ary[] = 'SELECT a.group_id, a.forum_id, a.auth_setting, a.auth_option_id, ao.auth_option - FROM ' . ACL_GROUPS_TABLE . ' a, ' . ACL_OPTIONS_TABLE . ' ao - WHERE a.auth_role_id = 0 - AND a.auth_option_id = ao.auth_option_id ' . - (($sql_group) ? 'AND a.' . $sql_group : '') . " - $sql_forum - $sql_opts - ORDER BY a.forum_id, ao.auth_option"; - - // Now grab group settings - role specific... - $sql_ary[] = 'SELECT a.group_id, a.forum_id, r.auth_setting, r.auth_option_id, ao.auth_option - FROM ' . ACL_GROUPS_TABLE . ' a, ' . ACL_ROLES_DATA_TABLE . ' r, ' . ACL_OPTIONS_TABLE . ' ao - WHERE a.auth_role_id = r.role_id - AND r.auth_option_id = ao.auth_option_id ' . - (($sql_group) ? 'AND a.' . $sql_group : '') . " - $sql_forum - $sql_opts - ORDER BY a.forum_id, ao.auth_option"; - - foreach ($sql_ary as $sql) - { - $result = $db->sql_query($sql); - - while ($row = $db->sql_fetchrow($result)) - { - $hold_ary[$row['group_id']][$row['forum_id']][$row['auth_option']] = $row['auth_setting']; - } - $db->sql_freeresult($result); - } - - return $hold_ary; - } - - /** - * Get raw acl data based on user for caching user_permissions - * This function returns the same data as acl_raw_data(), but without the user id as the first key within the array. - */ - function acl_raw_data_single_user($user_id) - { - global $db, $cache; - - // Check if the role-cache is there - if (($this->role_cache = $cache->get('_role_cache')) === false) - { - $this->role_cache = array(); - - // We pre-fetch roles - $sql = 'SELECT * - FROM ' . ACL_ROLES_DATA_TABLE . ' - ORDER BY role_id ASC'; - $result = $db->sql_query($sql); - - while ($row = $db->sql_fetchrow($result)) - { - $this->role_cache[$row['role_id']][$row['auth_option_id']] = (int) $row['auth_setting']; - } - $db->sql_freeresult($result); - - foreach ($this->role_cache as $role_id => $role_options) - { - $this->role_cache[$role_id] = serialize($role_options); - } - - $cache->put('_role_cache', $this->role_cache); - } - - $hold_ary = array(); - - // Grab user-specific permission settings - $sql = 'SELECT forum_id, auth_option_id, auth_role_id, auth_setting - FROM ' . ACL_USERS_TABLE . ' - WHERE user_id = ' . $user_id; - $result = $db->sql_query($sql); - - while ($row = $db->sql_fetchrow($result)) - { - // If a role is assigned, assign all options included within this role. Else, only set this one option. - if ($row['auth_role_id']) - { - $hold_ary[$row['forum_id']] = (empty($hold_ary[$row['forum_id']])) ? unserialize($this->role_cache[$row['auth_role_id']]) : $hold_ary[$row['forum_id']] + unserialize($this->role_cache[$row['auth_role_id']]); - } - else - { - $hold_ary[$row['forum_id']][$row['auth_option_id']] = $row['auth_setting']; - } - } - $db->sql_freeresult($result); - - // Now grab group-specific permission settings - $sql = 'SELECT a.forum_id, a.auth_option_id, a.auth_role_id, a.auth_setting - FROM ' . ACL_GROUPS_TABLE . ' a, ' . USER_GROUP_TABLE . ' ug, ' . GROUPS_TABLE . ' g - WHERE a.group_id = ug.group_id - AND g.group_id = ug.group_id - AND ug.user_pending = 0 - AND NOT (ug.group_leader = 1 AND g.group_skip_auth = 1) - AND ug.user_id = ' . $user_id; - $result = $db->sql_query($sql); - - while ($row = $db->sql_fetchrow($result)) - { - if (!$row['auth_role_id']) - { - $this->_set_group_hold_ary($hold_ary[$row['forum_id']], $row['auth_option_id'], $row['auth_setting']); - } - else if (!empty($this->role_cache[$row['auth_role_id']])) - { - foreach (unserialize($this->role_cache[$row['auth_role_id']]) as $option_id => $setting) - { - $this->_set_group_hold_ary($hold_ary[$row['forum_id']], $option_id, $setting); - } - } - } - $db->sql_freeresult($result); - - return $hold_ary; - } - - /** - * Private function snippet for setting a specific piece of the hold_ary - */ - function _set_group_hold_ary(&$hold_ary, $option_id, $setting) - { - if (!isset($hold_ary[$option_id]) || (isset($hold_ary[$option_id]) && $hold_ary[$option_id] != ACL_NEVER)) - { - $hold_ary[$option_id] = $setting; - - // If we detect ACL_NEVER, we will unset the flag option (within building the bitstring it is correctly set again) - if ($setting == ACL_NEVER) - { - $flag = substr($this->acl_options['option'][$option_id], 0, strpos($this->acl_options['option'][$option_id], '_') + 1); - $flag = (int) $this->acl_options['id'][$flag]; - - if (isset($hold_ary[$flag]) && $hold_ary[$flag] == ACL_YES) - { - unset($hold_ary[$flag]); - -/* This is uncommented, because i suspect this being slightly wrong due to mixed permission classes being possible - if (in_array(ACL_YES, $hold_ary)) - { - $hold_ary[$flag] = ACL_YES; - }*/ - } - } - } - } - - /** - * Authentication plug-ins is largely down to Sergey Kanareykin, our thanks to him. - */ - function login($username, $password, $autologin = false, $viewonline = 1, $admin = 0) - { - global $config, $db, $user, $phpbb_root_path, $phpEx, $phpbb_container; - - $method = trim(basename($config['auth_method'])); - - $provider = $phpbb_container->get('auth.provider.' . $method); - if ($provider) - { - $login = $provider->login($username, $password); - - // If the auth module wants us to create an empty profile do so and then treat the status as LOGIN_SUCCESS - if ($login['status'] == LOGIN_SUCCESS_CREATE_PROFILE) - { - // we are going to use the user_add function so include functions_user.php if it wasn't defined yet - if (!function_exists('user_add')) - { - include($phpbb_root_path . 'includes/functions_user.' . $phpEx); - } - - user_add($login['user_row'], (isset($login['cp_data'])) ? $login['cp_data'] : false); - - $sql = 'SELECT user_id, username, user_password, user_passchg, user_email, user_type - FROM ' . USERS_TABLE . " - WHERE username_clean = '" . $db->sql_escape(utf8_clean_string($username)) . "'"; - $result = $db->sql_query($sql); - $row = $db->sql_fetchrow($result); - $db->sql_freeresult($result); - - if (!$row) - { - return array( - 'status' => LOGIN_ERROR_EXTERNAL_AUTH, - 'error_msg' => 'AUTH_NO_PROFILE_CREATED', - 'user_row' => array('user_id' => ANONYMOUS), - ); - } - - $login = array( - 'status' => LOGIN_SUCCESS, - 'error_msg' => false, - 'user_row' => $row, - ); - } - - // If login succeeded, we will log the user in... else we pass the login array through... - if ($login['status'] == LOGIN_SUCCESS) - { - $old_session_id = $user->session_id; - - if ($admin) - { - global $SID, $_SID; - - $cookie_expire = time() - 31536000; - $user->set_cookie('u', '', $cookie_expire); - $user->set_cookie('sid', '', $cookie_expire); - unset($cookie_expire); - - $SID = '?sid='; - $user->session_id = $_SID = ''; - } - - $result = $user->session_create($login['user_row']['user_id'], $admin, $autologin, $viewonline); - - // Successful session creation - if ($result === true) - { - // If admin re-authentication we remove the old session entry because a new one has been created... - if ($admin) - { - // the login array is used because the user ids do not differ for re-authentication - $sql = 'DELETE FROM ' . SESSIONS_TABLE . " - WHERE session_id = '" . $db->sql_escape($old_session_id) . "' - AND session_user_id = {$login['user_row']['user_id']}"; - $db->sql_query($sql); - } - - return array( - 'status' => LOGIN_SUCCESS, - 'error_msg' => false, - 'user_row' => $login['user_row'], - ); - } - - return array( - 'status' => LOGIN_BREAK, - 'error_msg' => $result, - 'user_row' => $login['user_row'], - ); - } - - return $login; - } - - trigger_error('Authentication method not found', E_USER_ERROR); - } - - /** - * Fill auth_option statement for later querying based on the supplied options - */ - function build_auth_option_statement($key, $auth_options, &$sql_opts) - { - global $db; - - if (!is_array($auth_options)) - { - if (strpos($auth_options, '%') !== false) - { - $sql_opts = "AND $key " . $db->sql_like_expression(str_replace('%', $db->any_char, $auth_options)); - } - else - { - $sql_opts = "AND $key = '" . $db->sql_escape($auth_options) . "'"; - } - } - else - { - $is_like_expression = false; - - foreach ($auth_options as $option) - { - if (strpos($option, '%') !== false) - { - $is_like_expression = true; - } - } - - if (!$is_like_expression) - { - $sql_opts = 'AND ' . $db->sql_in_set($key, $auth_options); - } - else - { - $sql = array(); - - foreach ($auth_options as $option) - { - if (strpos($option, '%') !== false) - { - $sql[] = $key . ' ' . $db->sql_like_expression(str_replace('%', $db->any_char, $option)); - } - else - { - $sql[] = $key . " = '" . $db->sql_escape($option) . "'"; - } - } - - $sql_opts = 'AND (' . implode(' OR ', $sql) . ')'; - } - } - } -} diff --git a/phpBB/includes/auth/index.htm b/phpBB/includes/auth/index.htm deleted file mode 100644 index ee1f723a7d..0000000000 --- a/phpBB/includes/auth/index.htm +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/phpBB/includes/auth/provider/apache.php b/phpBB/includes/auth/provider/apache.php deleted file mode 100644 index 2e80436f78..0000000000 --- a/phpBB/includes/auth/provider/apache.php +++ /dev/null @@ -1,259 +0,0 @@ -db = $db; - $this->config = $config; - $this->request = $request; - $this->user = $user; - $this->phpbb_root_path = $phpbb_root_path; - $this->php_ext = $php_ext; - } - - /** - * {@inheritdoc} - */ - public function init() - { - if (!$this->request->is_set('PHP_AUTH_USER', phpbb_request_interface::SERVER) || $this->user->data['username'] !== htmlspecialchars_decode($this->request->server('PHP_AUTH_USER'))) - { - return $this->user->lang['APACHE_SETUP_BEFORE_USE']; - } - return false; - } - - /** - * {@inheritdoc} - */ - public function login($username, $password) - { - // do not allow empty password - if (!$password) - { - return array( - 'status' => LOGIN_ERROR_PASSWORD, - 'error_msg' => 'NO_PASSWORD_SUPPLIED', - 'user_row' => array('user_id' => ANONYMOUS), - ); - } - - if (!$username) - { - return array( - 'status' => LOGIN_ERROR_USERNAME, - 'error_msg' => 'LOGIN_ERROR_USERNAME', - 'user_row' => array('user_id' => ANONYMOUS), - ); - } - - if (!$this->request->is_set('PHP_AUTH_USER', phpbb_request_interface::SERVER)) - { - return array( - 'status' => LOGIN_ERROR_EXTERNAL_AUTH, - 'error_msg' => 'LOGIN_ERROR_EXTERNAL_AUTH_APACHE', - 'user_row' => array('user_id' => ANONYMOUS), - ); - } - - $php_auth_user = htmlspecialchars_decode($this->request->server('PHP_AUTH_USER')); - $php_auth_pw = htmlspecialchars_decode($this->request->server('PHP_AUTH_PW')); - - if (!empty($php_auth_user) && !empty($php_auth_pw)) - { - if ($php_auth_user !== $username) - { - return array( - 'status' => LOGIN_ERROR_USERNAME, - 'error_msg' => 'LOGIN_ERROR_USERNAME', - 'user_row' => array('user_id' => ANONYMOUS), - ); - } - - $sql = 'SELECT user_id, username, user_password, user_passchg, user_email, user_type - FROM ' . USERS_TABLE . " - WHERE username = '" . $this->db->sql_escape($php_auth_user) . "'"; - $result = $this->db->sql_query($sql); - $row = $this->db->sql_fetchrow($result); - $this->db->sql_freeresult($result); - - if ($row) - { - // User inactive... - if ($row['user_type'] == USER_INACTIVE || $row['user_type'] == USER_IGNORE) - { - return array( - 'status' => LOGIN_ERROR_ACTIVE, - 'error_msg' => 'ACTIVE_ERROR', - 'user_row' => $row, - ); - } - - // Successful login... - return array( - 'status' => LOGIN_SUCCESS, - 'error_msg' => false, - 'user_row' => $row, - ); - } - - // this is the user's first login so create an empty profile - return array( - 'status' => LOGIN_SUCCESS_CREATE_PROFILE, - 'error_msg' => false, - 'user_row' => user_row_apache($php_auth_user, $php_auth_pw), - ); - } - - // Not logged into apache - return array( - 'status' => LOGIN_ERROR_EXTERNAL_AUTH, - 'error_msg' => 'LOGIN_ERROR_EXTERNAL_AUTH_APACHE', - 'user_row' => array('user_id' => ANONYMOUS), - ); - } - - /** - * {@inheritdoc} - */ - public function autologin() - { - if (!$this->request->is_set('PHP_AUTH_USER', phpbb_request_interface::SERVER)) - { - return array(); - } - - $php_auth_user = htmlspecialchars_decode($this->request->server('PHP_AUTH_USER')); - $php_auth_pw = htmlspecialchars_decode($this->request->server('PHP_AUTH_PW')); - - if (!empty($php_auth_user) && !empty($php_auth_pw)) - { - set_var($php_auth_user, $php_auth_user, 'string', true); - set_var($php_auth_pw, $php_auth_pw, 'string', true); - - $sql = 'SELECT * - FROM ' . USERS_TABLE . " - WHERE username = '" . $this->db->sql_escape($php_auth_user) . "'"; - $result = $this->db->sql_query($sql); - $row = $this->db->sql_fetchrow($result); - $this->db->sql_freeresult($result); - - if ($row) - { - return ($row['user_type'] == USER_INACTIVE || $row['user_type'] == USER_IGNORE) ? array() : $row; - } - - if (!function_exists('user_add')) - { - include($this->phpbb_root_path . 'includes/functions_user.' . $this->php_ext); - } - - // create the user if he does not exist yet - user_add(user_row_apache($php_auth_user, $php_auth_pw)); - - $sql = 'SELECT * - FROM ' . USERS_TABLE . " - WHERE username_clean = '" . $this->db->sql_escape(utf8_clean_string($php_auth_user)) . "'"; - $result = $this->db->sql_query($sql); - $row = $this->db->sql_fetchrow($result); - $this->db->sql_freeresult($result); - - if ($row) - { - return $row; - } - } - - return array(); - } - - /** - * This function generates an array which can be passed to the user_add - * function in order to create a user - * - * @param string $username The username of the new user. - * @param string $password The password of the new user. - * @return array Contains data that can be passed directly to - * the user_add function. - */ - private function user_row($username, $password) - { - // first retrieve default group id - $sql = 'SELECT group_id - FROM ' . GROUPS_TABLE . " - WHERE group_name = '" . $this->db->sql_escape('REGISTERED') . "' - AND group_type = " . GROUP_SPECIAL; - $result = $this->db->sql_query($sql); - $row = $this->db->sql_fetchrow($result); - $this->db->sql_freeresult($result); - - if (!$row) - { - trigger_error('NO_GROUP'); - } - - // generate user account data - return array( - 'username' => $username, - 'user_password' => phpbb_hash($password), - 'user_email' => '', - 'group_id' => (int) $row['group_id'], - 'user_type' => USER_NORMAL, - 'user_ip' => $this->user->ip, - 'user_new' => ($this->config['new_member_post_limit']) ? 1 : 0, - ); - } - - /** - * {@inheritdoc} - */ - public function validate_session($user) - { - // Check if PHP_AUTH_USER is set and handle this case - if ($this->request->is_set('PHP_AUTH_USER', phpbb_request_interface::SERVER)) - { - $php_auth_user = $this->request->server('PHP_AUTH_USER'); - - return ($php_auth_user === $user['username']) ? true : false; - } - - // PHP_AUTH_USER is not set. A valid session is now determined by the user type (anonymous/bot or not) - if ($user['user_type'] == USER_IGNORE) - { - return true; - } - - return false; - } -} diff --git a/phpBB/includes/auth/provider/base.php b/phpBB/includes/auth/provider/base.php deleted file mode 100644 index 7eaf8bb2d3..0000000000 --- a/phpBB/includes/auth/provider/base.php +++ /dev/null @@ -1,72 +0,0 @@ -db = $db; - $this->config = $config; - $this->request = $request; - $this->user = $user; - $this->phpbb_root_path = $phpbb_root_path; - $this->php_ext = $php_ext; - } - - /** - * {@inheritdoc} - */ - public function login($username, $password) - { - // Auth plugins get the password untrimmed. - // For compatibility we trim() here. - $password = trim($password); - - // do not allow empty password - if (!$password) - { - return array( - 'status' => LOGIN_ERROR_PASSWORD, - 'error_msg' => 'NO_PASSWORD_SUPPLIED', - 'user_row' => array('user_id' => ANONYMOUS), - ); - } - - if (!$username) - { - return array( - 'status' => LOGIN_ERROR_USERNAME, - 'error_msg' => 'LOGIN_ERROR_USERNAME', - 'user_row' => array('user_id' => ANONYMOUS), - ); - } - - $username_clean = utf8_clean_string($username); - - $sql = 'SELECT user_id, username, user_password, user_passchg, user_pass_convert, user_email, user_type, user_login_attempts - FROM ' . USERS_TABLE . " - WHERE username_clean = '" . $this->db->sql_escape($username_clean) . "'"; - $result = $this->db->sql_query($sql); - $row = $this->db->sql_fetchrow($result); - $this->db->sql_freeresult($result); - - if (($this->user->ip && !$this->config['ip_login_limit_use_forwarded']) || - ($this->user->forwarded_for && $this->config['ip_login_limit_use_forwarded'])) - { - $sql = 'SELECT COUNT(*) AS attempts - FROM ' . LOGIN_ATTEMPT_TABLE . ' - WHERE attempt_time > ' . (time() - (int) $this->config['ip_login_limit_time']); - if ($this->config['ip_login_limit_use_forwarded']) - { - $sql .= " AND attempt_forwarded_for = '" . $this->db->sql_escape($this->user->forwarded_for) . "'"; - } - else - { - $sql .= " AND attempt_ip = '" . $this->db->sql_escape($this->user->ip) . "' "; - } - - $result = $this->db->sql_query($sql); - $attempts = (int) $this->db->sql_fetchfield('attempts'); - $this->db->sql_freeresult($result); - - $attempt_data = array( - 'attempt_ip' => $this->user->ip, - 'attempt_browser' => trim(substr($this->user->browser, 0, 149)), - 'attempt_forwarded_for' => $this->user->forwarded_for, - 'attempt_time' => time(), - 'user_id' => ($row) ? (int) $row['user_id'] : 0, - 'username' => $username, - 'username_clean' => $username_clean, - ); - $sql = 'INSERT INTO ' . LOGIN_ATTEMPT_TABLE . $this->db->sql_build_array('INSERT', $attempt_data); - $result = $this->db->sql_query($sql); - } - else - { - $attempts = 0; - } - - if (!$row) - { - if ($this->config['ip_login_limit_max'] && $attempts >= $this->config['ip_login_limit_max']) - { - return array( - 'status' => LOGIN_ERROR_ATTEMPTS, - 'error_msg' => 'LOGIN_ERROR_ATTEMPTS', - 'user_row' => array('user_id' => ANONYMOUS), - ); - } - - return array( - 'status' => LOGIN_ERROR_USERNAME, - 'error_msg' => 'LOGIN_ERROR_USERNAME', - 'user_row' => array('user_id' => ANONYMOUS), - ); - } - - $show_captcha = ($this->config['max_login_attempts'] && $row['user_login_attempts'] >= $this->config['max_login_attempts']) || - ($this->config['ip_login_limit_max'] && $attempts >= $this->config['ip_login_limit_max']); - - // If there are too many login attempts, we need to check for a confirm image - // Every auth module is able to define what to do by itself... - if ($show_captcha) - { - // Visual Confirmation handling - if (!class_exists('phpbb_captcha_factory', false)) - { - include ($this->phpbb_root_path . 'includes/captcha/captcha_factory.' . $this->php_ext); - } - - $captcha = phpbb_captcha_factory::get_instance($this->config['captcha_plugin']); - $captcha->init(CONFIRM_LOGIN); - $vc_response = $captcha->validate($row); - if ($vc_response) - { - return array( - 'status' => LOGIN_ERROR_ATTEMPTS, - 'error_msg' => 'LOGIN_ERROR_ATTEMPTS', - 'user_row' => $row, - ); - } - else - { - $captcha->reset(); - } - - } - - // If the password convert flag is set we need to convert it - if ($row['user_pass_convert']) - { - // enable super globals to get literal value - // this is needed to prevent unicode normalization - $super_globals_disabled = $this->request->super_globals_disabled(); - if ($super_globals_disabled) - { - $this->request->enable_super_globals(); - } - - // in phpBB2 passwords were used exactly as they were sent, with addslashes applied - $password_old_format = isset($_REQUEST['password']) ? (string) $_REQUEST['password'] : ''; - $password_old_format = (!STRIP) ? addslashes($password_old_format) : $password_old_format; - $password_new_format = $this->request->variable('password', '', true); - - if ($super_globals_disabled) - { - $this->request->disable_super_globals(); - } - - if ($password == $password_new_format) - { - if (!function_exists('utf8_to_cp1252')) - { - include($this->phpbb_root_path . 'includes/utf/data/recode_basic.' . $this->php_ext); - } - - // cp1252 is phpBB2's default encoding, characters outside ASCII range might work when converted into that encoding - // plain md5 support left in for conversions from other systems. - if ((strlen($row['user_password']) == 34 && (phpbb_check_hash(md5($password_old_format), $row['user_password']) || phpbb_check_hash(md5(utf8_to_cp1252($password_old_format)), $row['user_password']))) - || (strlen($row['user_password']) == 32 && (md5($password_old_format) == $row['user_password'] || md5(utf8_to_cp1252($password_old_format)) == $row['user_password']))) - { - $hash = phpbb_hash($password_new_format); - - // Update the password in the users table to the new format and remove user_pass_convert flag - $sql = 'UPDATE ' . USERS_TABLE . ' - SET user_password = \'' . $this->db->sql_escape($hash) . '\', - user_pass_convert = 0 - WHERE user_id = ' . $row['user_id']; - $this->db->sql_query($sql); - - $row['user_pass_convert'] = 0; - $row['user_password'] = $hash; - } - else - { - // Although we weren't able to convert this password we have to - // increase login attempt count to make sure this cannot be exploited - $sql = 'UPDATE ' . USERS_TABLE . ' - SET user_login_attempts = user_login_attempts + 1 - WHERE user_id = ' . (int) $row['user_id'] . ' - AND user_login_attempts < ' . LOGIN_ATTEMPTS_MAX; - $this->db->sql_query($sql); - - return array( - 'status' => LOGIN_ERROR_PASSWORD_CONVERT, - 'error_msg' => 'LOGIN_ERROR_PASSWORD_CONVERT', - 'user_row' => $row, - ); - } - } - } - - // Check password ... - if (!$row['user_pass_convert'] && phpbb_check_hash($password, $row['user_password'])) - { - // Check for old password hash... - if (strlen($row['user_password']) == 32) - { - $hash = phpbb_hash($password); - - // Update the password in the users table to the new format - $sql = 'UPDATE ' . USERS_TABLE . " - SET user_password = '" . $this->db->sql_escape($hash) . "', - user_pass_convert = 0 - WHERE user_id = {$row['user_id']}"; - $this->db->sql_query($sql); - - $row['user_password'] = $hash; - } - - $sql = 'DELETE FROM ' . LOGIN_ATTEMPT_TABLE . ' - WHERE user_id = ' . $row['user_id']; - $this->db->sql_query($sql); - - if ($row['user_login_attempts'] != 0) - { - // Successful, reset login attempts (the user passed all stages) - $sql = 'UPDATE ' . USERS_TABLE . ' - SET user_login_attempts = 0 - WHERE user_id = ' . $row['user_id']; - $this->db->sql_query($sql); - } - - // User inactive... - if ($row['user_type'] == USER_INACTIVE || $row['user_type'] == USER_IGNORE) - { - return array( - 'status' => LOGIN_ERROR_ACTIVE, - 'error_msg' => 'ACTIVE_ERROR', - 'user_row' => $row, - ); - } - - // Successful login... set user_login_attempts to zero... - return array( - 'status' => LOGIN_SUCCESS, - 'error_msg' => false, - 'user_row' => $row, - ); - } - - // Password incorrect - increase login attempts - $sql = 'UPDATE ' . USERS_TABLE . ' - SET user_login_attempts = user_login_attempts + 1 - WHERE user_id = ' . (int) $row['user_id'] . ' - AND user_login_attempts < ' . LOGIN_ATTEMPTS_MAX; - $this->db->sql_query($sql); - - // Give status about wrong password... - return array( - 'status' => ($show_captcha) ? LOGIN_ERROR_ATTEMPTS : LOGIN_ERROR_PASSWORD, - 'error_msg' => ($show_captcha) ? 'LOGIN_ERROR_ATTEMPTS' : 'LOGIN_ERROR_PASSWORD', - 'user_row' => $row, - ); - } -} diff --git a/phpBB/includes/auth/provider/index.htm b/phpBB/includes/auth/provider/index.htm deleted file mode 100644 index ee1f723a7d..0000000000 --- a/phpBB/includes/auth/provider/index.htm +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/phpBB/includes/auth/provider/interface.php b/phpBB/includes/auth/provider/interface.php deleted file mode 100644 index 47043bc107..0000000000 --- a/phpBB/includes/auth/provider/interface.php +++ /dev/null @@ -1,105 +0,0 @@ - status constant - * 'error_msg' => string - * 'user_row' => array - * ) - */ - public function login($username, $password); - - /** - * Autologin function - * - * @return array|null containing the user row, empty if no auto login - * should take place, or null if not impletmented. - */ - public function autologin(); - - /** - * This function is used to output any required fields in the authentication - * admin panel. It also defines any required configuration table fields. - * - * @return array|null Returns null if not implemented or an array of the - * configuration fields of the provider. - */ - public function acp(); - - /** - * This function updates the template with variables related to the acp - * options with whatever configuraton values are passed to it as an array. - * It then returns the name of the acp file related to this authentication - * provider. - * @param array $new_config Contains the new configuration values that - * have been set in acp_board. - * @return array|null Returns null if not implemented or an array with - * the template file name and an array of the vars - * that the template needs that must conform to the - * following example: - * array( - * 'TEMPLATE_FILE' => string, - * 'TEMPLATE_VARS' => array(...), - * ) - */ - public function get_acp_template($new_config); - - /** - * Performs additional actions during logout. - * - * @param array $data An array corresponding to - * phpbb_session::data - * @param boolean $new_session True for a new session, false for no new - * session. - */ - public function logout($data, $new_session); - - /** - * The session validation function checks whether the user is still logged - * into phpBB. - * - * @param array $user - * @return boolean true if the given user is authenticated, false if the - * session should be closed, or null if not implemented. - */ - public function validate_session($user); -} diff --git a/phpBB/includes/auth/provider/ldap.php b/phpBB/includes/auth/provider/ldap.php deleted file mode 100644 index 0196529408..0000000000 --- a/phpBB/includes/auth/provider/ldap.php +++ /dev/null @@ -1,346 +0,0 @@ -db = $db; - $this->config = $config; - $this->user = $user; - } - - /** - * {@inheritdoc} - */ - public function init() - { - if (!@extension_loaded('ldap')) - { - return $this->user->lang['LDAP_NO_LDAP_EXTENSION']; - } - - $this->config['ldap_port'] = (int) $this->config['ldap_port']; - if ($this->config['ldap_port']) - { - $ldap = @ldap_connect($this->config['ldap_server'], $this->config['ldap_port']); - } - else - { - $ldap = @ldap_connect($this->config['ldap_server']); - } - - if (!$ldap) - { - return $this->user->lang['LDAP_NO_SERVER_CONNECTION']; - } - - @ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3); - @ldap_set_option($ldap, LDAP_OPT_REFERRALS, 0); - - if ($this->config['ldap_user'] || $this->config['ldap_password']) - { - if (!@ldap_bind($ldap, htmlspecialchars_decode($this->config['ldap_user']), htmlspecialchars_decode($this->config['ldap_password']))) - { - return $this->user->lang['LDAP_INCORRECT_USER_PASSWORD']; - } - } - - // ldap_connect only checks whether the specified server is valid, so the connection might still fail - $search = @ldap_search( - $ldap, - htmlspecialchars_decode($this->config['ldap_base_dn']), - $this->ldap_user_filter($this->user->data['username']), - (empty($this->config['ldap_email'])) ? - array(htmlspecialchars_decode($this->config['ldap_uid'])) : - array(htmlspecialchars_decode($this->config['ldap_uid']), htmlspecialchars_decode($this->config['ldap_email'])), - 0, - 1 - ); - - if ($search === false) - { - return $this->user->lang['LDAP_SEARCH_FAILED']; - } - - $result = @ldap_get_entries($ldap, $search); - - @ldap_close($ldap); - - - if (!is_array($result) || sizeof($result) < 2) - { - return sprintf($this->user->lang['LDAP_NO_IDENTITY'], $this->user->data['username']); - } - - if (!empty($this->config['ldap_email']) && !isset($result[0][htmlspecialchars_decode($this->config['ldap_email'])])) - { - return $this->user->lang['LDAP_NO_EMAIL']; - } - - return false; - } - - /** - * {@inheritdoc} - */ - public function login($username, $password) - { - // do not allow empty password - if (!$password) - { - return array( - 'status' => LOGIN_ERROR_PASSWORD, - 'error_msg' => 'NO_PASSWORD_SUPPLIED', - 'user_row' => array('user_id' => ANONYMOUS), - ); - } - - if (!$username) - { - return array( - 'status' => LOGIN_ERROR_USERNAME, - 'error_msg' => 'LOGIN_ERROR_USERNAME', - 'user_row' => array('user_id' => ANONYMOUS), - ); - } - - if (!@extension_loaded('ldap')) - { - return array( - 'status' => LOGIN_ERROR_EXTERNAL_AUTH, - 'error_msg' => 'LDAP_NO_LDAP_EXTENSION', - 'user_row' => array('user_id' => ANONYMOUS), - ); - } - - $this->config['ldap_port'] = (int) $this->config['ldap_port']; - if ($this->config['ldap_port']) - { - $ldap = @ldap_connect($this->config['ldap_server'], $this->config['ldap_port']); - } - else - { - $ldap = @ldap_connect($this->config['ldap_server']); - } - - if (!$ldap) - { - return array( - 'status' => LOGIN_ERROR_EXTERNAL_AUTH, - 'error_msg' => 'LDAP_NO_SERVER_CONNECTION', - 'user_row' => array('user_id' => ANONYMOUS), - ); - } - - @ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3); - @ldap_set_option($ldap, LDAP_OPT_REFERRALS, 0); - - if ($this->config['ldap_user'] || $this->config['ldap_password']) - { - if (!@ldap_bind($ldap, htmlspecialchars_decode($this->config['ldap_user']), htmlspecialchars_decode($this->config['ldap_password']))) - { - return array( - 'status' => LOGIN_ERROR_EXTERNAL_AUTH, - 'error_msg' => 'LDAP_NO_SERVER_CONNECTION', - 'user_row' => array('user_id' => ANONYMOUS), - ); - } - } - - $search = @ldap_search( - $ldap, - htmlspecialchars_decode($this->config['ldap_base_dn']), - $this->ldap_user_filter($username), - (empty($this->config['ldap_email'])) ? - array(htmlspecialchars_decode($this->config['ldap_uid'])) : - array(htmlspecialchars_decode($this->config['ldap_uid']), htmlspecialchars_decode($this->config['ldap_email'])), - 0, - 1 - ); - - $ldap_result = @ldap_get_entries($ldap, $search); - - if (is_array($ldap_result) && sizeof($ldap_result) > 1) - { - if (@ldap_bind($ldap, $ldap_result[0]['dn'], htmlspecialchars_decode($password))) - { - @ldap_close($ldap); - - $sql ='SELECT user_id, username, user_password, user_passchg, user_email, user_type - FROM ' . USERS_TABLE . " - WHERE username_clean = '" . $this->db->sql_escape(utf8_clean_string($username)) . "'"; - $result = $this->db->sql_query($sql); - $row = $this->db->sql_fetchrow($result); - $this->db->sql_freeresult($result); - - if ($row) - { - unset($ldap_result); - - // User inactive... - if ($row['user_type'] == USER_INACTIVE || $row['user_type'] == USER_IGNORE) - { - return array( - 'status' => LOGIN_ERROR_ACTIVE, - 'error_msg' => 'ACTIVE_ERROR', - 'user_row' => $row, - ); - } - - // Successful login... set user_login_attempts to zero... - return array( - 'status' => LOGIN_SUCCESS, - 'error_msg' => false, - 'user_row' => $row, - ); - } - else - { - // retrieve default group id - $sql = 'SELECT group_id - FROM ' . GROUPS_TABLE . " - WHERE group_name = '" . $this->db->sql_escape('REGISTERED') . "' - AND group_type = " . GROUP_SPECIAL; - $result = $this->db->sql_query($sql); - $row = $this->db->sql_fetchrow($result); - $this->db->sql_freeresult($result); - - if (!$row) - { - trigger_error('NO_GROUP'); - } - - // generate user account data - $ldap_user_row = array( - 'username' => $username, - 'user_password' => phpbb_hash($password), - 'user_email' => (!empty($this->config['ldap_email'])) ? utf8_htmlspecialchars($ldap_result[0][htmlspecialchars_decode($this->config['ldap_email'])][0]) : '', - 'group_id' => (int) $row['group_id'], - 'user_type' => USER_NORMAL, - 'user_ip' => $this->user->ip, - 'user_new' => ($this->config['new_member_post_limit']) ? 1 : 0, - ); - - unset($ldap_result); - - // this is the user's first login so create an empty profile - return array( - 'status' => LOGIN_SUCCESS_CREATE_PROFILE, - 'error_msg' => false, - 'user_row' => $ldap_user_row, - ); - } - } - else - { - unset($ldap_result); - @ldap_close($ldap); - - // Give status about wrong password... - return array( - 'status' => LOGIN_ERROR_PASSWORD, - 'error_msg' => 'LOGIN_ERROR_PASSWORD', - 'user_row' => array('user_id' => ANONYMOUS), - ); - } - } - - @ldap_close($ldap); - - return array( - 'status' => LOGIN_ERROR_USERNAME, - 'error_msg' => 'LOGIN_ERROR_USERNAME', - 'user_row' => array('user_id' => ANONYMOUS), - ); - } - - /** - * {@inheritdoc} - */ - - public function acp() - { - // These are fields required in the config table - return array( - 'ldap_server', 'ldap_port', 'ldap_base_dn', 'ldap_uid', 'ldap_user_filter', 'ldap_email', 'ldap_user', 'ldap_password', - ); - } - - /** - * {@inheritdoc} - */ - public function get_acp_template($new_config) - { - return array( - 'TEMPLATE_FILE' => 'auth_provider_ldap.html', - 'TEMPLATE_VARS' => array( - 'AUTH_LDAP_DN' => $new_config['ldap_base_dn'], - 'AUTH_LDAP_EMAIL' => $new_config['ldap_email'], - 'AUTH_LDAP_PASSORD' => $new_config['ldap_password'], - 'AUTH_LDAP_PORT' => $new_config['ldap_port'], - 'AUTH_LDAP_SERVER' => $new_config['ldap_server'], - 'AUTH_LDAP_UID' => $new_config['ldap_uid'], - 'AUTH_LDAP_USER' => $new_config['ldap_user'], - 'AUTH_LDAP_USER_FILTER' => $new_config['ldap_user_filter'], - ), - ); - } - - /** - * Generates a filter string for ldap_search to find a user - * - * @param $username string Username identifying the searched user - * - * @return string A filter string for ldap_search - */ - private function ldap_user_filter($username) - { - $filter = '(' . $this->config['ldap_uid'] . '=' . $this->ldap_escape(htmlspecialchars_decode($username)) . ')'; - if ($this->config['ldap_user_filter']) - { - $_filter = ($this->config['ldap_user_filter'][0] == '(' && substr($this->config['ldap_user_filter'], -1) == ')') ? $this->config['ldap_user_filter'] : "({$this->config['ldap_user_filter']})"; - $filter = "(&{$filter}{$_filter})"; - } - return $filter; - } - - /** - * Escapes an LDAP AttributeValue - * - * @param string $string The string to be escaped - * @return string The escaped string - */ - private function ldap_escape($string) - { - return str_replace(array('*', '\\', '(', ')'), array('\\*', '\\\\', '\\(', '\\)'), $string); - } -} diff --git a/phpBB/includes/avatar/driver/driver.php b/phpBB/includes/avatar/driver/driver.php deleted file mode 100644 index 29c58d4e62..0000000000 --- a/phpBB/includes/avatar/driver/driver.php +++ /dev/null @@ -1,138 +0,0 @@ -config = $config; - $this->phpbb_root_path = $phpbb_root_path; - $this->php_ext = $php_ext; - $this->cache = $cache; - } - - /** - * @inheritdoc - */ - public function get_custom_html($user, $row, $alt = '') - { - return ''; - } - - /** - * @inheritdoc - */ - public function prepare_form_acp($user) - { - return array(); - } - - /** - * @inheritdoc - */ - public function delete($row) - { - return true; - } - - /** - * @inheritdoc - */ - public function get_template_name() - { - $driver = preg_replace('#^phpbb_avatar_driver_#', '', get_class($this)); - $template = "ucp_avatar_options_$driver.html"; - - return $template; - } - - /** - * @inheritdoc - */ - public function get_name() - { - return $this->name; - } - - /** - * Sets the name of the driver. - * - * @param string $name Driver name - */ - public function set_name($name) - { - $this->name = $name; - } -} diff --git a/phpBB/includes/avatar/driver/gravatar.php b/phpBB/includes/avatar/driver/gravatar.php deleted file mode 100644 index d559da1c0d..0000000000 --- a/phpBB/includes/avatar/driver/gravatar.php +++ /dev/null @@ -1,172 +0,0 @@ - $row['avatar'], - 'width' => $row['avatar_width'], - 'height' => $row['avatar_height'], - ); - } - - /** - * @inheritdoc - */ - public function get_custom_html($user, $row, $alt = '') - { - return ''; - } - - /** - * @inheritdoc - */ - public function prepare_form($request, $template, $user, $row, &$error) - { - $template->assign_vars(array( - 'AVATAR_GRAVATAR_WIDTH' => (($row['avatar_type'] == $this->get_name() || $row['avatar_type'] == 'gravatar') && $row['avatar_width']) ? $row['avatar_width'] : $request->variable('avatar_gravatar_width', 0), - 'AVATAR_GRAVATAR_HEIGHT' => (($row['avatar_type'] == $this->get_name() || $row['avatar_type'] == 'gravatar') && $row['avatar_height']) ? $row['avatar_height'] : $request->variable('avatar_gravatar_width', 0), - 'AVATAR_GRAVATAR_EMAIL' => (($row['avatar_type'] == $this->get_name() || $row['avatar_type'] == 'gravatar') && $row['avatar']) ? $row['avatar'] : '', - )); - - return true; - } - - /** - * @inheritdoc - */ - public function process_form($request, $template, $user, $row, &$error) - { - $row['avatar'] = $request->variable('avatar_gravatar_email', ''); - $row['avatar_width'] = $request->variable('avatar_gravatar_width', 0); - $row['avatar_height'] = $request->variable('avatar_gravatar_height', 0); - - if (!function_exists('validate_data')) - { - require($this->phpbb_root_path . 'includes/functions_user.' . $this->php_ext); - } - - $validate_array = validate_data( - array( - 'email' => $row['avatar'], - ), - array( - 'email' => array( - array('string', false, 6, 60), - array('email')) - ) - ); - - $error = array_merge($error, $validate_array); - - if (!empty($error)) - { - return false; - } - - // Make sure getimagesize works... - if (function_exists('getimagesize') && ($row['avatar_width'] <= 0 || $row['avatar_height'] <= 0)) - { - /** - * default to the minimum of the maximum allowed avatar size if the size - * is not or only partially entered - */ - $row['avatar_width'] = $row['avatar_height'] = min($this->config['avatar_max_width'], $this->config['avatar_max_height']); - $url = $this->get_gravatar_url($row); - - if (($row['avatar_width'] <= 0 || $row['avatar_height'] <= 0) && (($image_data = getimagesize($url)) === false)) - { - $error[] = 'UNABLE_GET_IMAGE_SIZE'; - return false; - } - - if (!empty($image_data) && ($image_data[0] <= 0 || $image_data[1] <= 0)) - { - $error[] = 'AVATAR_NO_SIZE'; - return false; - } - - $row['avatar_width'] = ($row['avatar_width'] && $row['avatar_height']) ? $row['avatar_width'] : $image_data[0]; - $row['avatar_height'] = ($row['avatar_width'] && $row['avatar_height']) ? $row['avatar_height'] : $image_data[1]; - } - - if ($row['avatar_width'] <= 0 || $row['avatar_height'] <= 0) - { - $error[] = 'AVATAR_NO_SIZE'; - return false; - } - - if ($this->config['avatar_max_width'] || $this->config['avatar_max_height']) - { - if ($row['avatar_width'] > $this->config['avatar_max_width'] || $row['avatar_height'] > $this->config['avatar_max_height']) - { - $error[] = array('AVATAR_WRONG_SIZE', $this->config['avatar_min_width'], $this->config['avatar_min_height'], $this->config['avatar_max_width'], $this->config['avatar_max_height'], $row['avatar_width'], $row['avatar_height']); - return false; - } - } - - if ($this->config['avatar_min_width'] || $this->config['avatar_min_height']) - { - if ($row['avatar_width'] < $this->config['avatar_min_width'] || $row['avatar_height'] < $this->config['avatar_min_height']) - { - $error[] = array('AVATAR_WRONG_SIZE', $this->config['avatar_min_width'], $this->config['avatar_min_height'], $this->config['avatar_max_width'], $this->config['avatar_max_height'], $row['avatar_width'], $row['avatar_height']); - return false; - } - } - - return array( - 'avatar' => $row['avatar'], - 'avatar_width' => $row['avatar_width'], - 'avatar_height' => $row['avatar_height'], - ); - } - - /** - * Build gravatar URL for output on page - * - * @return string Gravatar URL - */ - protected function get_gravatar_url($row) - { - $url = self::GRAVATAR_URL; - $url .= md5(strtolower(trim($row['avatar']))); - - if ($row['avatar_width'] || $row['avatar_height']) - { - $url .= '?s=' . max($row['avatar_width'], $row['avatar_height']); - } - - return $url; - } -} diff --git a/phpBB/includes/avatar/driver/interface.php b/phpBB/includes/avatar/driver/interface.php deleted file mode 100644 index 3d62969aef..0000000000 --- a/phpBB/includes/avatar/driver/interface.php +++ /dev/null @@ -1,116 +0,0 @@ - '', 'width' => 0, 'height' => 0] - */ - public function get_data($row); - - /** - * Returns custom html if it is needed for displaying this avatar - * - * @param phpbb_user $user phpBB user object - * @param array $row User data or group data that has been cleaned with - * phpbb_avatar_manager::clean_row - * @param string $alt Alternate text for avatar image - * - * @return string HTML - */ - public function get_custom_html($user, $row, $alt = ''); - - /** - * Prepare form for changing the settings of this avatar - * - * @param phpbb_request $request Request object - * @param phpbb_template $template Template object - * @param phpbb_user $user User object - * @param array $row User data or group data that has been cleaned with - * phpbb_avatar_manager::clean_row - * @param array &$error Reference to an error array that is filled by this - * function. Key values can either be a string with a language key or - * an array that will be passed to vsprintf() with the language key in - * the first array key. - * - * @return bool True if form has been successfully prepared - */ - public function prepare_form($request, $template, $user, $row, &$error); - - /** - * Prepare form for changing the acp settings of this avatar - * - * @param phpbb_user $user phpBB user object - * - * @return array Array of configuration options as consumed by acp_board. - * The setting for enabling/disabling the avatar will be handled by - * the avatar manager. - */ - public function prepare_form_acp($user); - - /** - * Process form data - * - * @param phpbb_request $request Request object - * @param phpbb_template $template Template object - * @param phpbb_user $user User object - * @param array $row User data or group data that has been cleaned with - * phpbb_avatar_manager::clean_row - * @param array &$error Reference to an error array that is filled by this - * function. Key values can either be a string with a language key or - * an array that will be passed to vsprintf() with the language key in - * the first array key. - * - * @return array Array containing the avatar data as follows: - * ['avatar'], ['avatar_width'], ['avatar_height'] - */ - public function process_form($request, $template, $user, $row, &$error); - - /** - * Delete avatar - * - * @param array $row User data or group data that has been cleaned with - * phpbb_avatar_manager::clean_row - * - * @return bool True if avatar has been deleted or there is no need to delete, - * i.e. when the avatar is not hosted locally. - */ - public function delete($row); - - /** - * Get the avatar driver's template name - * - * @return string Avatar driver's template name - */ - public function get_template_name(); -} diff --git a/phpBB/includes/avatar/driver/local.php b/phpBB/includes/avatar/driver/local.php deleted file mode 100644 index f4bcd4ce74..0000000000 --- a/phpBB/includes/avatar/driver/local.php +++ /dev/null @@ -1,197 +0,0 @@ - $this->phpbb_root_path . $this->config['avatar_gallery_path'] . '/' . $row['avatar'], - 'width' => $row['avatar_width'], - 'height' => $row['avatar_height'], - ); - } - - /** - * @inheritdoc - */ - public function prepare_form($request, $template, $user, $row, &$error) - { - $avatar_list = $this->get_avatar_list($user); - $category = $request->variable('avatar_local_cat', ''); - - foreach ($avatar_list as $cat => $null) - { - if (!empty($avatar_list[$cat])) - { - $template->assign_block_vars('avatar_local_cats', array( - 'NAME' => $cat, - 'SELECTED' => ($cat == $category), - )); - } - - if ($cat != $category) - { - unset($avatar_list[$cat]); - } - } - - if (!empty($avatar_list[$category])) - { - $template->assign_vars(array( - 'AVATAR_LOCAL_SHOW' => true, - )); - - $table_cols = isset($row['avatar_gallery_cols']) ? $row['avatar_gallery_cols'] : 4; - $row_count = $col_count = $avatar_pos = 0; - $avatar_count = sizeof($avatar_list[$category]); - - reset($avatar_list[$category]); - - while ($avatar_pos < $avatar_count) - { - $img = current($avatar_list[$category]); - next($avatar_list[$category]); - - if ($col_count == 0) - { - ++$row_count; - $template->assign_block_vars('avatar_local_row', array( - )); - } - - $template->assign_block_vars('avatar_local_row.avatar_local_col', array( - 'AVATAR_IMAGE' => $this->phpbb_root_path . $this->config['avatar_gallery_path'] . '/' . $img['file'], - 'AVATAR_NAME' => $img['name'], - 'AVATAR_FILE' => $img['filename'], - )); - - $template->assign_block_vars('avatar_local_row.avatar_local_option', array( - 'AVATAR_FILE' => $img['filename'], - 'S_OPTIONS_AVATAR' => $img['filename'] - )); - - $col_count = ($col_count + 1) % $table_cols; - - ++$avatar_pos; - } - } - - return true; - } - - /** - * @inheritdoc - */ - public function prepare_form_acp($user) - { - return array( - 'avatar_gallery_path' => array('lang' => 'AVATAR_GALLERY_PATH', 'validate' => 'rpath', 'type' => 'text:20:255', 'explain' => true), - ); - } - - /** - * @inheritdoc - */ - public function process_form($request, $template, $user, $row, &$error) - { - $avatar_list = $this->get_avatar_list($user); - $category = $request->variable('avatar_local_cat', ''); - - $file = $request->variable('avatar_local_file', ''); - - if (empty($category) || empty($file)) - { - $error[] = 'NO_AVATAR_SELECTED'; - return false; - } - - if (!isset($avatar_list[$category][urldecode($file)])) - { - $error[] = 'AVATAR_URL_NOT_FOUND'; - return false; - } - - return array( - 'avatar' => ($category != $user->lang['MAIN']) ? $category . '/' . $file : $file, - 'avatar_width' => $avatar_list[$category][urldecode($file)]['width'], - 'avatar_height' => $avatar_list[$category][urldecode($file)]['height'], - ); - } - - /** - * Get a list of avatars that are locally available - * Results get cached for 24 hours (86400 seconds) - * - * @param phpbb_user $user User object - * - * @return array Array containing the locally available avatars - */ - protected function get_avatar_list($user) - { - $avatar_list = ($this->cache == null) ? false : $this->cache->get('avatar_local_list'); - - if ($avatar_list === false) - { - $avatar_list = array(); - $path = $this->phpbb_root_path . $this->config['avatar_gallery_path']; - - $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS | FilesystemIterator::UNIX_PATHS), RecursiveIteratorIterator::SELF_FIRST); - foreach ($iterator as $file_info) - { - $file_path = $file_info->getPath(); - $image = $file_info->getFilename(); - - // Match all images in the gallery folder - if (preg_match('#^[^&\'"<>]+\.(?:' . implode('|', $this->allowed_extensions) . ')$#i', $image) && is_file($file_path . '/' . $image)) - { - if (function_exists('getimagesize')) - { - $dims = getimagesize($file_path . '/' . $image); - } - else - { - $dims = array(0, 0); - } - $cat = ($path == $file_path) ? $user->lang['MAIN'] : str_replace("$path/", '', $file_path); - $avatar_list[$cat][$image] = array( - 'file' => ($cat != $user->lang['MAIN']) ? rawurlencode($cat) . '/' . rawurlencode($image) : rawurlencode($image), - 'filename' => rawurlencode($image), - 'name' => ucfirst(str_replace('_', ' ', preg_replace('#^(.*)\..*$#', '\1', $image))), - 'width' => $dims[0], - 'height' => $dims[1], - ); - } - } - ksort($avatar_list); - - if ($this->cache != null) - { - $this->cache->put('avatar_local_list', $avatar_list, 86400); - } - } - - return $avatar_list; - } -} diff --git a/phpBB/includes/avatar/driver/remote.php b/phpBB/includes/avatar/driver/remote.php deleted file mode 100644 index 7da58107a1..0000000000 --- a/phpBB/includes/avatar/driver/remote.php +++ /dev/null @@ -1,164 +0,0 @@ - $row['avatar'], - 'width' => $row['avatar_width'], - 'height' => $row['avatar_height'], - ); - } - - /** - * @inheritdoc - */ - public function prepare_form($request, $template, $user, $row, &$error) - { - $template->assign_vars(array( - 'AVATAR_REMOTE_WIDTH' => ((in_array($row['avatar_type'], array(AVATAR_REMOTE, $this->get_name(), 'remote'))) && $row['avatar_width']) ? $row['avatar_width'] : $request->variable('avatar_remote_width', 0), - 'AVATAR_REMOTE_HEIGHT' => ((in_array($row['avatar_type'], array(AVATAR_REMOTE, $this->get_name(), 'remote'))) && $row['avatar_height']) ? $row['avatar_height'] : $request->variable('avatar_remote_width', 0), - 'AVATAR_REMOTE_URL' => ((in_array($row['avatar_type'], array(AVATAR_REMOTE, $this->get_name(), 'remote'))) && $row['avatar']) ? $row['avatar'] : '', - )); - - return true; - } - - /** - * @inheritdoc - */ - public function process_form($request, $template, $user, $row, &$error) - { - $url = $request->variable('avatar_remote_url', ''); - $width = $request->variable('avatar_remote_width', 0); - $height = $request->variable('avatar_remote_height', 0); - - if (!preg_match('#^(http|https|ftp)://#i', $url)) - { - $url = 'http://' . $url; - } - - if (!function_exists('validate_data')) - { - require($this->phpbb_root_path . 'includes/functions_user.' . $this->php_ext); - } - - $validate_array = validate_data( - array( - 'url' => $url, - ), - array( - 'url' => array('string', true, 5, 255), - ) - ); - - $error = array_merge($error, $validate_array); - - if (!empty($error)) - { - return false; - } - - // Check if this url looks alright - // This isn't perfect, but it's what phpBB 3.0 did, and might as well make sure everything is compatible - if (!preg_match('#^(http|https|ftp)://(?:(.*?\.)*?[a-z0-9\-]+?\.[a-z]{2,4}|(?:\d{1,3}\.){3,5}\d{1,3}):?([0-9]*?).*?\.('. implode('|', $this->allowed_extensions) . ')$#i', $url)) - { - $error[] = 'AVATAR_URL_INVALID'; - return false; - } - - // Make sure getimagesize works... - if (function_exists('getimagesize')) - { - if (($width <= 0 || $height <= 0) && (($image_data = getimagesize($url)) === false)) - { - $error[] = 'UNABLE_GET_IMAGE_SIZE'; - return false; - } - - if (!empty($image_data) && ($image_data[0] <= 0 || $image_data[1] <= 0)) - { - $error[] = 'AVATAR_NO_SIZE'; - return false; - } - - $width = ($width && $height) ? $width : $image_data[0]; - $height = ($width && $height) ? $height : $image_data[1]; - } - - if ($width <= 0 || $height <= 0) - { - $error[] = 'AVATAR_NO_SIZE'; - return false; - } - - if (!class_exists('fileupload')) - { - include($this->phpbb_root_path . 'includes/functions_upload.' . $this->php_ext); - } - - $types = fileupload::image_types(); - $extension = strtolower(filespec::get_extension($url)); - - if (!empty($image_data) && (!isset($types[$image_data[2]]) || !in_array($extension, $types[$image_data[2]]))) - { - if (!isset($types[$image_data[2]])) - { - $error[] = 'UNABLE_GET_IMAGE_SIZE'; - } - else - { - $error[] = array('IMAGE_FILETYPE_MISMATCH', $types[$image_data[2]][0], $extension); - } - - return false; - } - - if ($this->config['avatar_max_width'] || $this->config['avatar_max_height']) - { - if ($width > $this->config['avatar_max_width'] || $height > $this->config['avatar_max_height']) - { - $error[] = array('AVATAR_WRONG_SIZE', $this->config['avatar_min_width'], $this->config['avatar_min_height'], $this->config['avatar_max_width'], $this->config['avatar_max_height'], $width, $height); - return false; - } - } - - if ($this->config['avatar_min_width'] || $this->config['avatar_min_height']) - { - if ($width < $this->config['avatar_min_width'] || $height < $this->config['avatar_min_height']) - { - $error[] = array('AVATAR_WRONG_SIZE', $this->config['avatar_min_width'], $this->config['avatar_min_height'], $this->config['avatar_max_width'], $this->config['avatar_max_height'], $width, $height); - return false; - } - } - - return array( - 'avatar' => $url, - 'avatar_width' => $width, - 'avatar_height' => $height, - ); - } -} diff --git a/phpBB/includes/avatar/driver/upload.php b/phpBB/includes/avatar/driver/upload.php deleted file mode 100644 index 685ac4f349..0000000000 --- a/phpBB/includes/avatar/driver/upload.php +++ /dev/null @@ -1,185 +0,0 @@ - $this->phpbb_root_path . 'download/file.' . $this->php_ext . '?avatar=' . $row['avatar'], - 'width' => $row['avatar_width'], - 'height' => $row['avatar_height'], - ); - } - - /** - * @inheritdoc - */ - public function prepare_form($request, $template, $user, $row, &$error) - { - if (!$this->can_upload()) - { - return false; - } - - $template->assign_vars(array( - 'S_UPLOAD_AVATAR_URL' => ($this->config['allow_avatar_remote_upload']) ? true : false, - 'AVATAR_UPLOAD_SIZE' => $this->config['avatar_filesize'], - )); - - return true; - } - - /** - * @inheritdoc - */ - public function process_form($request, $template, $user, $row, &$error) - { - if (!$this->can_upload()) - { - return false; - } - - if (!class_exists('fileupload')) - { - include($this->phpbb_root_path . 'includes/functions_upload.' . $this->php_ext); - } - - $upload = new fileupload('AVATAR_', $this->allowed_extensions, $this->config['avatar_filesize'], $this->config['avatar_min_width'], $this->config['avatar_min_height'], $this->config['avatar_max_width'], $this->config['avatar_max_height'], (isset($this->config['mime_triggers']) ? explode('|', $this->config['mime_triggers']) : false)); - - $url = $request->variable('avatar_upload_url', ''); - $upload_file = $request->file('avatar_upload_file'); - - if (!empty($upload_file['name'])) - { - $file = $upload->form_upload('avatar_upload_file'); - } - elseif (!empty($this->config['allow_avatar_remote_upload']) && !empty($url)) - { - if (!preg_match('#^(http|https|ftp)://#i', $url)) - { - $url = 'http://' . $url; - } - - if (!function_exists('validate_data')) - { - require($this->phpbb_root_path . 'includes/functions_user.' . $this->php_ext); - } - - $validate_array = validate_data( - array( - 'url' => $url, - ), - array( - 'url' => array('string', true, 5, 255), - ) - ); - - $error = array_merge($error, $validate_array); - - if (!empty($error)) - { - return false; - } - - $file = $upload->remote_upload($url); - } - else - { - $error[] = 'NO_AVATAR_SELECTED'; - return false; - } - - $prefix = $this->config['avatar_salt'] . '_'; - $file->clean_filename('avatar', $prefix, $row['id']); - - $destination = $this->config['avatar_path']; - - // Adjust destination path (no trailing slash) - if (substr($destination, -1, 1) == '/' || substr($destination, -1, 1) == '\\') - { - $destination = substr($destination, 0, -1); - } - - $destination = str_replace(array('../', '..\\', './', '.\\'), '', $destination); - if ($destination && ($destination[0] == '/' || $destination[0] == "\\")) - { - $destination = ''; - } - - // Move file and overwrite any existing image - $file->move_file($destination, true); - - if (sizeof($file->error)) - { - $file->remove(); - $error = array_merge($error, $file->error); - return false; - } - - return array( - 'avatar' => $row['id'] . '_' . time() . '.' . $file->get('extension'), - 'avatar_width' => $file->get('width'), - 'avatar_height' => $file->get('height'), - ); - } - - /** - * @inheritdoc - */ - public function prepare_form_acp($user) - { - return array( - 'allow_avatar_remote_upload'=> array('lang' => 'ALLOW_REMOTE_UPLOAD', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), - 'avatar_filesize' => array('lang' => 'MAX_FILESIZE', 'validate' => 'int:0', 'type' => 'number:0', 'explain' => true, 'append' => ' ' . $user->lang['BYTES']), - 'avatar_path' => array('lang' => 'AVATAR_STORAGE_PATH', 'validate' => 'rwpath', 'type' => 'text:20:255', 'explain' => true), - ); - } - - /** - * @inheritdoc - */ - public function delete($row) - { - $ext = substr(strrchr($row['avatar'], '.'), 1); - $filename = $this->phpbb_root_path . $this->config['avatar_path'] . '/' . $this->config['avatar_salt'] . '_' . $row['id'] . '.' . $ext; - - if (file_exists($filename)) - { - @unlink($filename); - } - - return true; - } - - /** - * Check if user is able to upload an avatar - * - * @return bool True if user can upload, false if not - */ - protected function can_upload() - { - return (file_exists($this->phpbb_root_path . $this->config['avatar_path']) && phpbb_is_writable($this->phpbb_root_path . $this->config['avatar_path']) && (@ini_get('file_uploads') || strtolower(@ini_get('file_uploads')) == 'on')); - } -} diff --git a/phpBB/includes/avatar/manager.php b/phpBB/includes/avatar/manager.php deleted file mode 100644 index 58d994c3c0..0000000000 --- a/phpBB/includes/avatar/manager.php +++ /dev/null @@ -1,309 +0,0 @@ - '', - 'avatar_type' => '', - 'avatar_width' => '', - 'avatar_height' => '', - ); - - /** - * Construct an avatar manager object - * - * @param phpbb_config $config phpBB configuration - * @param array $avatar_drivers Avatar drivers passed via the service container - * @param object $container Container object - */ - public function __construct(phpbb_config $config, $avatar_drivers, $container) - { - $this->config = $config; - $this->avatar_drivers = $avatar_drivers; - $this->container = $container; - } - - /** - * Get the driver object specified by the avatar type - * - * @param string $avatar_type Avatar type; by default an avatar's service container name - * @param bool $load_enabled Load only enabled avatars - * - * @return object Avatar driver object - */ - public function get_driver($avatar_type, $load_enabled = true) - { - if (self::$enabled_drivers === false) - { - $this->load_enabled_drivers(); - } - - $avatar_drivers = ($load_enabled) ? self::$enabled_drivers : $this->get_all_drivers(); - - // Legacy stuff... - switch ($avatar_type) - { - case AVATAR_GALLERY: - $avatar_type = 'avatar.driver.local'; - break; - case AVATAR_UPLOAD: - $avatar_type = 'avatar.driver.upload'; - break; - case AVATAR_REMOTE: - $avatar_type = 'avatar.driver.remote'; - break; - } - - if (!isset($avatar_drivers[$avatar_type])) - { - return null; - } - - /* - * There is no need to handle invalid avatar types as the following code - * will cause a ServiceNotFoundException if the type does not exist - */ - $driver = $this->container->get($avatar_type); - - return $driver; - } - - /** - * Load the list of enabled drivers - * This is executed once and fills self::$enabled_drivers - */ - protected function load_enabled_drivers() - { - if (!empty($this->avatar_drivers)) - { - self::$enabled_drivers = array(); - foreach ($this->avatar_drivers as $driver) - { - if ($this->is_enabled($driver)) - { - self::$enabled_drivers[$driver->get_name()] = $driver->get_name(); - } - } - asort(self::$enabled_drivers); - } - } - - /** - * Get a list of all avatar drivers - * - * As this function will only be called in the ACP avatar settings page, it - * doesn't make much sense to cache the list of all avatar drivers like the - * list of the enabled drivers. - * - * @return array Array containing a list of all avatar drivers - */ - public function get_all_drivers() - { - $drivers = array(); - - if (!empty($this->avatar_drivers)) - { - foreach ($this->avatar_drivers as $driver) - { - $drivers[$driver->get_name()] = $driver->get_name(); - } - asort($drivers); - } - - return $drivers; - } - - /** - * Get a list of enabled avatar drivers - * - * @return array Array containing a list of the enabled avatar drivers - */ - public function get_enabled_drivers() - { - if (self::$enabled_drivers === false) - { - $this->load_enabled_drivers(); - } - - return self::$enabled_drivers; - } - - /** - * Strip out user_ and group_ prefixes from keys - * - * @param array $row User data or group data - * - * @return array User data or group data with keys that have been - * stripped from the preceding "user_" or "group_" - */ - static public function clean_row($row) - { - // Upon creation of a user/group $row might be empty - if (empty($row)) - { - return self::$default_row; - } - - $keys = array_keys($row); - $values = array_values($row); - - $keys = array_map(array('phpbb_avatar_manager', 'strip_prefix'), $keys); - - return array_combine($keys, $values); - } - - /** - * Strip prepending user_ or group_ prefix from key - * - * @param string Array key - * @return string Key that has been stripped from its prefix - */ - static protected function strip_prefix($key) - { - return preg_replace('#^(?:user_|group_)#', '', $key); - } - - /** - * Clean driver names that are returned from template files - * Underscores are replaced with dots - * - * @param string $name Driver name - * - * @return string Cleaned driver name - */ - static public function clean_driver_name($name) - { - return str_replace('_', '.', $name); - } - - /** - * Prepare driver names for use in template files - * Dots are replaced with underscores - * - * @param string $name Clean driver name - * - * @return string Prepared driver name - */ - static public function prepare_driver_name($name) - { - return str_replace('.', '_', $name); - } - - /** - * Check if avatar is enabled - * - * @param object $driver Avatar driver object - * - * @return bool True if avatar is enabled, false if it's disabled - */ - public function is_enabled($driver) - { - $config_name = $this->get_driver_config_name($driver); - - return $this->config["allow_avatar_{$config_name}"]; - } - - /** - * Get the settings array for enabling/disabling an avatar driver - * - * @param object $driver Avatar driver object - * - * @return array Array of configuration options as consumed by acp_board - */ - public function get_avatar_settings($driver) - { - $config_name = $this->get_driver_config_name($driver); - - return array( - 'allow_avatar_' . $config_name => array('lang' => 'ALLOW_' . strtoupper($config_name), 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => false), - ); - } - - /** - * Get the config name of an avatar driver - * - * @param object $driver Avatar driver object - * - * @return string Avatar driver config name - */ - public function get_driver_config_name($driver) - { - return preg_replace('#^phpbb_avatar_driver_#', '', get_class($driver)); - } - - /** - * Replace "error" strings with their real, localized form - * - * @param phpbb_user phpBB User object - * @param array $error Array containing error strings - * Key values can either be a string with a language key or an array - * that will be passed to vsprintf() with the language key in the - * first array key. - * - * @return array Array containing the localized error strings - */ - public function localize_errors(phpbb_user $user, $error) - { - foreach ($error as $key => $lang) - { - if (is_array($lang)) - { - $lang_key = array_shift($lang); - $error[$key] = vsprintf($user->lang($lang_key), $lang); - } - else - { - $error[$key] = $user->lang("$lang"); - } - } - - return $error; - } -} diff --git a/phpBB/includes/cache/driver/apc.php b/phpBB/includes/cache/driver/apc.php deleted file mode 100644 index 0516b669c8..0000000000 --- a/phpBB/includes/cache/driver/apc.php +++ /dev/null @@ -1,75 +0,0 @@ -key_prefix . $var); - } - - /** - * Store data in the cache - * - * @access protected - * @param string $var Cache key - * @param mixed $data Data to store - * @param int $ttl Time-to-live of cached data - * @return bool True if the operation succeeded - */ - function _write($var, $data, $ttl = 2592000) - { - return apc_store($this->key_prefix . $var, $data, $ttl); - } - - /** - * Remove an item from the cache - * - * @access protected - * @param string $var Cache key - * @return bool True if the operation succeeded - */ - function _delete($var) - { - return apc_delete($this->key_prefix . $var); - } -} diff --git a/phpBB/includes/cache/driver/base.php b/phpBB/includes/cache/driver/base.php deleted file mode 100644 index 32e04f813a..0000000000 --- a/phpBB/includes/cache/driver/base.php +++ /dev/null @@ -1,23 +0,0 @@ -key_prefix - eaccelerator_rm(substr($var['name'], 1)); - } - - parent::purge(); - } - - /** - * Perform cache garbage collection - * - * @return null - */ - function tidy() - { - eaccelerator_gc(); - - set_config('cache_last_gc', time(), true); - } - - /** - * Fetch an item from the cache - * - * @access protected - * @param string $var Cache key - * @return mixed Cached data - */ - function _read($var) - { - $result = eaccelerator_get($this->key_prefix . $var); - - if ($result === null) - { - return false; - } - - // Handle serialized objects - if (is_string($result) && strpos($result, $this->serialize_header . 'O:') === 0) - { - $result = unserialize(substr($result, strlen($this->serialize_header))); - } - - return $result; - } - - /** - * Store data in the cache - * - * @access protected - * @param string $var Cache key - * @param mixed $data Data to store - * @param int $ttl Time-to-live of cached data - * @return bool True if the operation succeeded - */ - function _write($var, $data, $ttl = 2592000) - { - // Serialize objects and make them easy to detect - $data = (is_object($data)) ? $this->serialize_header . serialize($data) : $data; - - return eaccelerator_put($this->key_prefix . $var, $data, $ttl); - } - - /** - * Remove an item from the cache - * - * @access protected - * @param string $var Cache key - * @return bool True if the operation succeeded - */ - function _delete($var) - { - return eaccelerator_rm($this->key_prefix . $var); - } -} diff --git a/phpBB/includes/cache/driver/file.php b/phpBB/includes/cache/driver/file.php deleted file mode 100644 index 85decbe3e8..0000000000 --- a/phpBB/includes/cache/driver/file.php +++ /dev/null @@ -1,740 +0,0 @@ -cache_dir = !is_null($cache_dir) ? $cache_dir : $phpbb_root_path . 'cache/'; - } - - /** - * Load global cache - */ - function load() - { - return $this->_read('data_global'); - } - - /** - * Unload cache object - */ - function unload() - { - $this->save(); - unset($this->vars); - unset($this->var_expires); - unset($this->sql_rowset); - unset($this->sql_row_pointer); - - $this->vars = array(); - $this->var_expires = array(); - $this->sql_rowset = array(); - $this->sql_row_pointer = array(); - } - - /** - * Save modified objects - */ - function save() - { - if (!$this->is_modified) - { - return; - } - - global $phpEx; - - if (!$this->_write('data_global')) - { - if (!function_exists('phpbb_is_writable')) - { - global $phpbb_root_path; - include($phpbb_root_path . 'includes/functions.' . $phpEx); - } - - // Now, this occurred how often? ... phew, just tell the user then... - if (!phpbb_is_writable($this->cache_dir)) - { - // We need to use die() here, because else we may encounter an infinite loop (the message handler calls $cache->unload()) - die('Fatal: ' . $this->cache_dir . ' is NOT writable.'); - exit; - } - - die('Fatal: Not able to open ' . $this->cache_dir . 'data_global.' . $phpEx); - exit; - } - - $this->is_modified = false; - } - - /** - * Tidy cache - */ - function tidy() - { - global $phpEx; - - $dir = @opendir($this->cache_dir); - - if (!$dir) - { - return; - } - - $time = time(); - - while (($entry = readdir($dir)) !== false) - { - if (!preg_match('/^(sql_|data_(?!global))/', $entry)) - { - continue; - } - - if (!($handle = @fopen($this->cache_dir . $entry, 'rb'))) - { - continue; - } - - // Skip the PHP header - fgets($handle); - - // Skip expiration - $expires = (int) fgets($handle); - - fclose($handle); - - if ($time >= $expires) - { - $this->remove_file($this->cache_dir . $entry); - } - } - closedir($dir); - - if (file_exists($this->cache_dir . 'data_global.' . $phpEx)) - { - if (!sizeof($this->vars)) - { - $this->load(); - } - - foreach ($this->var_expires as $var_name => $expires) - { - if ($time >= $expires) - { - $this->destroy($var_name); - } - } - } - - set_config('cache_last_gc', time(), true); - } - - /** - * Get saved cache object - */ - function get($var_name) - { - if ($var_name[0] == '_') - { - global $phpEx; - - if (!$this->_exists($var_name)) - { - return false; - } - - return $this->_read('data' . $var_name); - } - else - { - return ($this->_exists($var_name)) ? $this->vars[$var_name] : false; - } - } - - /** - * Put data into cache - */ - function put($var_name, $var, $ttl = 31536000) - { - if ($var_name[0] == '_') - { - $this->_write('data' . $var_name, $var, time() + $ttl); - } - else - { - $this->vars[$var_name] = $var; - $this->var_expires[$var_name] = time() + $ttl; - $this->is_modified = true; - } - } - - /** - * Purge cache data - */ - function purge() - { - // Purge all phpbb cache files - $dir = @opendir($this->cache_dir); - - if (!$dir) - { - return; - } - - while (($entry = readdir($dir)) !== false) - { - if (strpos($entry, 'container_') !== 0 && - strpos($entry, 'url_matcher') !== 0 && - strpos($entry, 'sql_') !== 0 && - strpos($entry, 'data_') !== 0 && - strpos($entry, 'ctpl_') !== 0 && - strpos($entry, 'tpl_') !== 0) - { - continue; - } - - $this->remove_file($this->cache_dir . $entry); - } - closedir($dir); - - unset($this->vars); - unset($this->var_expires); - unset($this->sql_rowset); - unset($this->sql_row_pointer); - - $this->vars = array(); - $this->var_expires = array(); - $this->sql_rowset = array(); - $this->sql_row_pointer = array(); - - $this->is_modified = false; - } - - /** - * Destroy cache data - */ - function destroy($var_name, $table = '') - { - global $phpEx; - - if ($var_name == 'sql' && !empty($table)) - { - if (!is_array($table)) - { - $table = array($table); - } - - $dir = @opendir($this->cache_dir); - - if (!$dir) - { - return; - } - - while (($entry = readdir($dir)) !== false) - { - if (strpos($entry, 'sql_') !== 0) - { - continue; - } - - if (!($handle = @fopen($this->cache_dir . $entry, 'rb'))) - { - continue; - } - - // Skip the PHP header - fgets($handle); - - // Skip expiration - fgets($handle); - - // Grab the query, remove the LF - $query = substr(fgets($handle), 0, -1); - - fclose($handle); - - foreach ($table as $check_table) - { - // Better catch partial table names than no table names. ;) - if (strpos($query, $check_table) !== false) - { - $this->remove_file($this->cache_dir . $entry); - break; - } - } - } - closedir($dir); - - return; - } - - if (!$this->_exists($var_name)) - { - return; - } - - if ($var_name[0] == '_') - { - $this->remove_file($this->cache_dir . 'data' . $var_name . ".$phpEx", true); - } - else if (isset($this->vars[$var_name])) - { - $this->is_modified = true; - unset($this->vars[$var_name]); - unset($this->var_expires[$var_name]); - - // We save here to let the following cache hits succeed - $this->save(); - } - } - - /** - * Check if a given cache entry exist - */ - function _exists($var_name) - { - if ($var_name[0] == '_') - { - global $phpEx; - return file_exists($this->cache_dir . 'data' . $var_name . ".$phpEx"); - } - else - { - if (!sizeof($this->vars)) - { - $this->load(); - } - - if (!isset($this->var_expires[$var_name])) - { - return false; - } - - return (time() > $this->var_expires[$var_name]) ? false : isset($this->vars[$var_name]); - } - } - - /** - * Load cached sql query - */ - function sql_load($query) - { - // Remove extra spaces and tabs - $query = preg_replace('/[\n\r\s\t]+/', ' ', $query); - - if (($rowset = $this->_read('sql_' . md5($query))) === false) - { - return false; - } - - $query_id = sizeof($this->sql_rowset); - $this->sql_rowset[$query_id] = $rowset; - $this->sql_row_pointer[$query_id] = 0; - - return $query_id; - } - - /** - * {@inheritDoc} - */ - function sql_save(phpbb_db_driver $db, $query, $query_result, $ttl) - { - // Remove extra spaces and tabs - $query = preg_replace('/[\n\r\s\t]+/', ' ', $query); - - $query_id = sizeof($this->sql_rowset); - $this->sql_rowset[$query_id] = array(); - $this->sql_row_pointer[$query_id] = 0; - - while ($row = $db->sql_fetchrow($query_result)) - { - $this->sql_rowset[$query_id][] = $row; - } - $db->sql_freeresult($query_result); - - if ($this->_write('sql_' . md5($query), $this->sql_rowset[$query_id], $ttl + time(), $query)) - { - return $query_id; - } - - return $query_result; - } - - /** - * Ceck if a given sql query exist in cache - */ - function sql_exists($query_id) - { - return isset($this->sql_rowset[$query_id]); - } - - /** - * Fetch row from cache (database) - */ - function sql_fetchrow($query_id) - { - if ($this->sql_row_pointer[$query_id] < sizeof($this->sql_rowset[$query_id])) - { - return $this->sql_rowset[$query_id][$this->sql_row_pointer[$query_id]++]; - } - - return false; - } - - /** - * Fetch a field from the current row of a cached database result (database) - */ - function sql_fetchfield($query_id, $field) - { - if ($this->sql_row_pointer[$query_id] < sizeof($this->sql_rowset[$query_id])) - { - return (isset($this->sql_rowset[$query_id][$this->sql_row_pointer[$query_id]][$field])) ? $this->sql_rowset[$query_id][$this->sql_row_pointer[$query_id]++][$field] : false; - } - - return false; - } - - /** - * Seek a specific row in an a cached database result (database) - */ - function sql_rowseek($rownum, $query_id) - { - if ($rownum >= sizeof($this->sql_rowset[$query_id])) - { - return false; - } - - $this->sql_row_pointer[$query_id] = $rownum; - return true; - } - - /** - * Free memory used for a cached database result (database) - */ - function sql_freeresult($query_id) - { - if (!isset($this->sql_rowset[$query_id])) - { - return false; - } - - unset($this->sql_rowset[$query_id]); - unset($this->sql_row_pointer[$query_id]); - - return true; - } - - /** - * Read cached data from a specified file - * - * @access private - * @param string $filename Filename to write - * @return mixed False if an error was encountered, otherwise the data type of the cached data - */ - function _read($filename) - { - global $phpEx; - - $file = "{$this->cache_dir}$filename.$phpEx"; - - $type = substr($filename, 0, strpos($filename, '_')); - - if (!file_exists($file)) - { - return false; - } - - if (!($handle = @fopen($file, 'rb'))) - { - return false; - } - - // Skip the PHP header - fgets($handle); - - if ($filename == 'data_global') - { - $this->vars = $this->var_expires = array(); - - $time = time(); - - while (($expires = (int) fgets($handle)) && !feof($handle)) - { - // Number of bytes of data - $bytes = substr(fgets($handle), 0, -1); - - if (!is_numeric($bytes) || ($bytes = (int) $bytes) === 0) - { - // We cannot process the file without a valid number of bytes - // so we discard it - fclose($handle); - - $this->vars = $this->var_expires = array(); - $this->is_modified = false; - - $this->remove_file($file); - - return false; - } - - if ($time >= $expires) - { - fseek($handle, $bytes, SEEK_CUR); - - continue; - } - - $var_name = substr(fgets($handle), 0, -1); - - // Read the length of bytes that consists of data. - $data = fread($handle, $bytes - strlen($var_name)); - $data = @unserialize($data); - - // Don't use the data if it was invalid - if ($data !== false) - { - $this->vars[$var_name] = $data; - $this->var_expires[$var_name] = $expires; - } - - // Absorb the LF - fgets($handle); - } - - fclose($handle); - - $this->is_modified = false; - - return true; - } - else - { - $data = false; - $line = 0; - - while (($buffer = fgets($handle)) && !feof($handle)) - { - $buffer = substr($buffer, 0, -1); // Remove the LF - - // $buffer is only used to read integers - // if it is non numeric we have an invalid - // cache file, which we will now remove. - if (!is_numeric($buffer)) - { - break; - } - - if ($line == 0) - { - $expires = (int) $buffer; - - if (time() >= $expires) - { - break; - } - - if ($type == 'sql') - { - // Skip the query - fgets($handle); - } - } - else if ($line == 1) - { - $bytes = (int) $buffer; - - // Never should have 0 bytes - if (!$bytes) - { - break; - } - - // Grab the serialized data - $data = fread($handle, $bytes); - - // Read 1 byte, to trigger EOF - fread($handle, 1); - - if (!feof($handle)) - { - // Somebody tampered with our data - $data = false; - } - break; - } - else - { - // Something went wrong - break; - } - $line++; - } - fclose($handle); - - // unserialize if we got some data - $data = ($data !== false) ? @unserialize($data) : $data; - - if ($data === false) - { - $this->remove_file($file); - return false; - } - - return $data; - } - } - - /** - * Write cache data to a specified file - * - * 'data_global' is a special case and the generated format is different for this file: - * - * - * (expiration) - * (length of var and serialised data) - * (var) - * (serialised data) - * ... (repeat) - * - * - * The other files have a similar format: - * - * - * (expiration) - * (query) [SQL files only] - * (length of serialised data) - * (serialised data) - * - * - * @access private - * @param string $filename Filename to write - * @param mixed $data Data to store - * @param int $expires Timestamp when the data expires - * @param string $query Query when caching SQL queries - * @return bool True if the file was successfully created, otherwise false - */ - function _write($filename, $data = null, $expires = 0, $query = '') - { - global $phpEx; - - $file = "{$this->cache_dir}$filename.$phpEx"; - - $lock = new phpbb_lock_flock($file); - $lock->acquire(); - - if ($handle = @fopen($file, 'wb')) - { - // File header - fwrite($handle, '<' . '?php exit; ?' . '>'); - - if ($filename == 'data_global') - { - // Global data is a different format - foreach ($this->vars as $var => $data) - { - if (strpos($var, "\r") !== false || strpos($var, "\n") !== false) - { - // CR/LF would cause fgets() to read the cache file incorrectly - // do not cache test entries, they probably won't be read back - // the cache keys should really be alphanumeric with a few symbols. - continue; - } - $data = serialize($data); - - // Write out the expiration time - fwrite($handle, "\n" . $this->var_expires[$var] . "\n"); - - // Length of the remaining data for this var (ignoring two LF's) - fwrite($handle, strlen($data . $var) . "\n"); - fwrite($handle, $var . "\n"); - fwrite($handle, $data); - } - } - else - { - fwrite($handle, "\n" . $expires . "\n"); - - if (strpos($filename, 'sql_') === 0) - { - fwrite($handle, $query . "\n"); - } - $data = serialize($data); - - fwrite($handle, strlen($data) . "\n"); - fwrite($handle, $data); - } - - fclose($handle); - - if (!function_exists('phpbb_chmod')) - { - global $phpbb_root_path; - include($phpbb_root_path . 'includes/functions.' . $phpEx); - } - - phpbb_chmod($file, CHMOD_READ | CHMOD_WRITE); - - $return_value = true; - } - else - { - $return_value = false; - } - - $lock->release(); - - return $return_value; - } - - /** - * Removes/unlinks file - */ - function remove_file($filename, $check = false) - { - if (!function_exists('phpbb_is_writable')) - { - global $phpbb_root_path, $phpEx; - include($phpbb_root_path . 'includes/functions.' . $phpEx); - } - - if ($check && !phpbb_is_writable($this->cache_dir)) - { - // E_USER_ERROR - not using language entry - intended. - trigger_error('Unable to remove files within ' . $this->cache_dir . '. Please check directory permissions.', E_USER_ERROR); - } - - return @unlink($filename); - } -} diff --git a/phpBB/includes/cache/driver/interface.php b/phpBB/includes/cache/driver/interface.php deleted file mode 100644 index 53f684d1c8..0000000000 --- a/phpBB/includes/cache/driver/interface.php +++ /dev/null @@ -1,144 +0,0 @@ -memcache = new Memcache; - foreach(explode(',', PHPBB_ACM_MEMCACHE) as $u) - { - $parts = explode('/', $u); - $this->memcache->addServer(trim($parts[0]), trim($parts[1])); - } - $this->flags = (PHPBB_ACM_MEMCACHE_COMPRESS) ? MEMCACHE_COMPRESSED : 0; - } - - /** - * Unload the cache resources - * - * @return null - */ - function unload() - { - parent::unload(); - - $this->memcache->close(); - } - - /** - * Purge cache data - * - * @return null - */ - function purge() - { - $this->memcache->flush(); - - parent::purge(); - } - - /** - * Fetch an item from the cache - * - * @access protected - * @param string $var Cache key - * @return mixed Cached data - */ - function _read($var) - { - return $this->memcache->get($this->key_prefix . $var); - } - - /** - * Store data in the cache - * - * @access protected - * @param string $var Cache key - * @param mixed $data Data to store - * @param int $ttl Time-to-live of cached data - * @return bool True if the operation succeeded - */ - function _write($var, $data, $ttl = 2592000) - { - if (!$this->memcache->replace($this->key_prefix . $var, $data, $this->flags, $ttl)) - { - return $this->memcache->set($this->key_prefix . $var, $data, $this->flags, $ttl); - } - return true; - } - - /** - * Remove an item from the cache - * - * @access protected - * @param string $var Cache key - * @return bool True if the operation succeeded - */ - function _delete($var) - { - return $this->memcache->delete($this->key_prefix . $var); - } -} diff --git a/phpBB/includes/cache/driver/memory.php b/phpBB/includes/cache/driver/memory.php deleted file mode 100644 index f77a1df316..0000000000 --- a/phpBB/includes/cache/driver/memory.php +++ /dev/null @@ -1,439 +0,0 @@ -cache_dir = $phpbb_root_path . 'cache/'; - $this->key_prefix = substr(md5($dbname . $table_prefix), 0, 8) . '_'; - - if (!isset($this->extension) || !extension_loaded($this->extension)) - { - global $acm_type; - - trigger_error("Could not find required extension [{$this->extension}] for the ACM module $acm_type.", E_USER_ERROR); - } - - if (isset($this->function) && !function_exists($this->function)) - { - global $acm_type; - - trigger_error("The required function [{$this->function}] is not available for the ACM module $acm_type.", E_USER_ERROR); - } - } - - /** - * Load global cache - */ - function load() - { - // grab the global cache - $this->vars = $this->_read('global'); - - if ($this->vars !== false) - { - return true; - } - - return false; - } - - /** - * Unload cache object - */ - function unload() - { - $this->save(); - unset($this->vars); - unset($this->sql_rowset); - unset($this->sql_row_pointer); - - $this->vars = array(); - $this->sql_rowset = array(); - $this->sql_row_pointer = array(); - } - - /** - * Save modified objects - */ - function save() - { - if (!$this->is_modified) - { - return; - } - - $this->_write('global', $this->vars, 2592000); - - $this->is_modified = false; - } - - /** - * Tidy cache - */ - function tidy() - { - // cache has auto GC, no need to have any code here :) - - set_config('cache_last_gc', time(), true); - } - - /** - * Get saved cache object - */ - function get($var_name) - { - if ($var_name[0] == '_') - { - if (!$this->_exists($var_name)) - { - return false; - } - - return $this->_read($var_name); - } - else - { - return ($this->_exists($var_name)) ? $this->vars[$var_name] : false; - } - } - - /** - * Put data into cache - */ - function put($var_name, $var, $ttl = 2592000) - { - if ($var_name[0] == '_') - { - $this->_write($var_name, $var, $ttl); - } - else - { - $this->vars[$var_name] = $var; - $this->is_modified = true; - } - } - - /** - * Purge cache data - */ - function purge() - { - // Purge all phpbb cache files - $dir = @opendir($this->cache_dir); - - if (!$dir) - { - return; - } - - while (($entry = readdir($dir)) !== false) - { - if (strpos($entry, 'container_') !== 0 && - strpos($entry, 'url_matcher') !== 0 && - strpos($entry, 'sql_') !== 0 && - strpos($entry, 'data_') !== 0 && - strpos($entry, 'ctpl_') !== 0 && - strpos($entry, 'tpl_') !== 0) - { - continue; - } - - $this->remove_file($this->cache_dir . $entry); - } - closedir($dir); - - unset($this->vars); - unset($this->sql_rowset); - unset($this->sql_row_pointer); - - $this->vars = array(); - $this->sql_rowset = array(); - $this->sql_row_pointer = array(); - - $this->is_modified = false; - } - - - /** - * Destroy cache data - */ - function destroy($var_name, $table = '') - { - if ($var_name == 'sql' && !empty($table)) - { - if (!is_array($table)) - { - $table = array($table); - } - - foreach ($table as $table_name) - { - // gives us the md5s that we want - $temp = $this->_read('sql_' . $table_name); - - if ($temp === false) - { - continue; - } - - // delete each query ref - foreach ($temp as $md5_id => $void) - { - $this->_delete('sql_' . $md5_id); - } - - // delete the table ref - $this->_delete('sql_' . $table_name); - } - - return; - } - - if (!$this->_exists($var_name)) - { - return; - } - - if ($var_name[0] == '_') - { - $this->_delete($var_name); - } - else if (isset($this->vars[$var_name])) - { - $this->is_modified = true; - unset($this->vars[$var_name]); - - // We save here to let the following cache hits succeed - $this->save(); - } - } - - /** - * Check if a given cache entry exist - */ - function _exists($var_name) - { - if ($var_name[0] == '_') - { - return $this->_isset($var_name); - } - else - { - if (!sizeof($this->vars)) - { - $this->load(); - } - - return isset($this->vars[$var_name]); - } - } - - /** - * Load cached sql query - */ - function sql_load($query) - { - // Remove extra spaces and tabs - $query = preg_replace('/[\n\r\s\t]+/', ' ', $query); - $query_id = sizeof($this->sql_rowset); - - if (($result = $this->_read('sql_' . md5($query))) === false) - { - return false; - } - - $this->sql_rowset[$query_id] = $result; - $this->sql_row_pointer[$query_id] = 0; - - return $query_id; - } - - /** - * {@inheritDoc} - */ - function sql_save(phpbb_db_driver $db, $query, $query_result, $ttl) - { - // Remove extra spaces and tabs - $query = preg_replace('/[\n\r\s\t]+/', ' ', $query); - $hash = md5($query); - - // determine which tables this query belongs to - // Some queries use backticks, namely the get_database_size() query - // don't check for conformity, the SQL would error and not reach here. - if (!preg_match('/FROM \\(?(`?\\w+`?(?: \\w+)?(?:, ?`?\\w+`?(?: \\w+)?)*)\\)?/', $query, $regs)) - { - // Bail out if the match fails. - return $query_result; - } - $tables = array_map('trim', explode(',', $regs[1])); - - foreach ($tables as $table_name) - { - // Remove backticks - $table_name = ($table_name[0] == '`') ? substr($table_name, 1, -1) : $table_name; - - if (($pos = strpos($table_name, ' ')) !== false) - { - $table_name = substr($table_name, 0, $pos); - } - - $temp = $this->_read('sql_' . $table_name); - - if ($temp === false) - { - $temp = array(); - } - - $temp[$hash] = true; - - // This must never expire - $this->_write('sql_' . $table_name, $temp, 0); - } - - // store them in the right place - $query_id = sizeof($this->sql_rowset); - $this->sql_rowset[$query_id] = array(); - $this->sql_row_pointer[$query_id] = 0; - - while ($row = $db->sql_fetchrow($query_result)) - { - $this->sql_rowset[$query_id][] = $row; - } - $db->sql_freeresult($query_result); - - $this->_write('sql_' . $hash, $this->sql_rowset[$query_id], $ttl); - - return $query_id; - } - - /** - * Ceck if a given sql query exist in cache - */ - function sql_exists($query_id) - { - return isset($this->sql_rowset[$query_id]); - } - - /** - * Fetch row from cache (database) - */ - function sql_fetchrow($query_id) - { - if ($this->sql_row_pointer[$query_id] < sizeof($this->sql_rowset[$query_id])) - { - return $this->sql_rowset[$query_id][$this->sql_row_pointer[$query_id]++]; - } - - return false; - } - - /** - * Fetch a field from the current row of a cached database result (database) - */ - function sql_fetchfield($query_id, $field) - { - if ($this->sql_row_pointer[$query_id] < sizeof($this->sql_rowset[$query_id])) - { - return (isset($this->sql_rowset[$query_id][$this->sql_row_pointer[$query_id]][$field])) ? $this->sql_rowset[$query_id][$this->sql_row_pointer[$query_id]++][$field] : false; - } - - return false; - } - - /** - * Seek a specific row in an a cached database result (database) - */ - function sql_rowseek($rownum, $query_id) - { - if ($rownum >= sizeof($this->sql_rowset[$query_id])) - { - return false; - } - - $this->sql_row_pointer[$query_id] = $rownum; - return true; - } - - /** - * Free memory used for a cached database result (database) - */ - function sql_freeresult($query_id) - { - if (!isset($this->sql_rowset[$query_id])) - { - return false; - } - - unset($this->sql_rowset[$query_id]); - unset($this->sql_row_pointer[$query_id]); - - return true; - } - - /** - * Removes/unlinks file - */ - function remove_file($filename, $check = false) - { - if (!function_exists('phpbb_is_writable')) - { - global $phpbb_root_path, $phpEx; - include($phpbb_root_path . 'includes/functions.' . $phpEx); - } - - if ($check && !phpbb_is_writable($this->cache_dir)) - { - // E_USER_ERROR - not using language entry - intended. - trigger_error('Unable to remove files within ' . $this->cache_dir . '. Please check directory permissions.', E_USER_ERROR); - } - - return @unlink($filename); - } - - /** - * Check if a cache var exists - * - * @access protected - * @param string $var Cache key - * @return bool True if it exists, otherwise false - */ - function _isset($var) - { - // Most caches don't need to check - return true; - } -} diff --git a/phpBB/includes/cache/driver/null.php b/phpBB/includes/cache/driver/null.php deleted file mode 100644 index 2fadc27ba3..0000000000 --- a/phpBB/includes/cache/driver/null.php +++ /dev/null @@ -1,154 +0,0 @@ -redis = new Redis(); - - $args = func_get_args(); - if (!empty($args)) - { - $ok = call_user_func_array(array($this->redis, 'connect'), $args); - } - else - { - $ok = $this->redis->connect(PHPBB_ACM_REDIS_HOST, PHPBB_ACM_REDIS_PORT); - } - - if (!$ok) - { - trigger_error('Could not connect to redis server'); - } - - if (defined('PHPBB_ACM_REDIS_PASSWORD')) - { - if (!$this->redis->auth(PHPBB_ACM_REDIS_PASSWORD)) - { - global $acm_type; - - trigger_error("Incorrect password for the ACM module $acm_type.", E_USER_ERROR); - } - } - - $this->redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP); - $this->redis->setOption(Redis::OPT_PREFIX, $this->key_prefix); - - if (defined('PHPBB_ACM_REDIS_DB')) - { - if (!$this->redis->select(PHPBB_ACM_REDIS_DB)) - { - global $acm_type; - - trigger_error("Incorrect database for the ACM module $acm_type.", E_USER_ERROR); - } - } - } - - /** - * Unload the cache resources - * - * @return null - */ - function unload() - { - parent::unload(); - - $this->redis->close(); - } - - /** - * Purge cache data - * - * @return null - */ - function purge() - { - $this->redis->flushDB(); - - parent::purge(); - } - - /** - * Fetch an item from the cache - * - * @access protected - * @param string $var Cache key - * @return mixed Cached data - */ - function _read($var) - { - return $this->redis->get($var); - } - - /** - * Store data in the cache - * - * @access protected - * @param string $var Cache key - * @param mixed $data Data to store - * @param int $ttl Time-to-live of cached data - * @return bool True if the operation succeeded - */ - function _write($var, $data, $ttl = 2592000) - { - return $this->redis->setex($var, $ttl, $data); - } - - /** - * Remove an item from the cache - * - * @access protected - * @param string $var Cache key - * @return bool True if the operation succeeded - */ - function _delete($var) - { - if ($this->redis->delete($var) > 0) - { - return true; - } - return false; - } -} - diff --git a/phpBB/includes/cache/driver/wincache.php b/phpBB/includes/cache/driver/wincache.php deleted file mode 100644 index 58f3b4a581..0000000000 --- a/phpBB/includes/cache/driver/wincache.php +++ /dev/null @@ -1,78 +0,0 @@ -key_prefix . $var, $success); - - return ($success) ? $result : false; - } - - /** - * Store data in the cache - * - * @access protected - * @param string $var Cache key - * @param mixed $data Data to store - * @param int $ttl Time-to-live of cached data - * @return bool True if the operation succeeded - */ - function _write($var, $data, $ttl = 2592000) - { - return wincache_ucache_set($this->key_prefix . $var, $data, $ttl); - } - - /** - * Remove an item from the cache - * - * @access protected - * @param string $var Cache key - * @return bool True if the operation succeeded - */ - function _delete($var) - { - return wincache_ucache_delete($this->key_prefix . $var); - } -} diff --git a/phpBB/includes/cache/driver/xcache.php b/phpBB/includes/cache/driver/xcache.php deleted file mode 100644 index 06c5fafd97..0000000000 --- a/phpBB/includes/cache/driver/xcache.php +++ /dev/null @@ -1,112 +0,0 @@ - 0 -* - xcache.admin.enable_auth = off (or xcache.admin.user and xcache.admin.password set) -* -*/ -class phpbb_cache_driver_xcache extends phpbb_cache_driver_memory -{ - var $extension = 'XCache'; - - function __construct() - { - parent::__construct(); - - if (!function_exists('ini_get') || (int) ini_get('xcache.var_size') <= 0) - { - trigger_error('Increase xcache.var_size setting above 0 or enable ini_get() to use this ACM module.', E_USER_ERROR); - } - } - - /** - * Purge cache data - * - * @return null - */ - function purge() - { - // Run before for XCache, if admin functions are disabled it will terminate execution - parent::purge(); - - // If the admin authentication is enabled but not set up, this will cause a nasty error. - // Not much we can do about it though. - $n = xcache_count(XC_TYPE_VAR); - - for ($i = 0; $i < $n; $i++) - { - xcache_clear_cache(XC_TYPE_VAR, $i); - } - } - - /** - * Fetch an item from the cache - * - * @access protected - * @param string $var Cache key - * @return mixed Cached data - */ - function _read($var) - { - $result = xcache_get($this->key_prefix . $var); - - return ($result !== null) ? $result : false; - } - - /** - * Store data in the cache - * - * @access protected - * @param string $var Cache key - * @param mixed $data Data to store - * @param int $ttl Time-to-live of cached data - * @return bool True if the operation succeeded - */ - function _write($var, $data, $ttl = 2592000) - { - return xcache_set($this->key_prefix . $var, $data, $ttl); - } - - /** - * Remove an item from the cache - * - * @access protected - * @param string $var Cache key - * @return bool True if the operation succeeded - */ - function _delete($var) - { - return xcache_unset($this->key_prefix . $var); - } - - /** - * Check if a cache var exists - * - * @access protected - * @param string $var Cache key - * @return bool True if it exists, otherwise false - */ - function _isset($var) - { - return xcache_isset($this->key_prefix . $var); - } -} diff --git a/phpBB/includes/cache/service.php b/phpBB/includes/cache/service.php deleted file mode 100644 index 69c5e0fdd0..0000000000 --- a/phpBB/includes/cache/service.php +++ /dev/null @@ -1,406 +0,0 @@ -set_driver($driver); - $this->config = $config; - $this->db = $db; - $this->phpbb_root_path = $phpbb_root_path; - $this->php_ext = $php_ext; - } - - /** - * Returns the cache driver used by this cache service. - * - * @return phpbb_cache_driver_interface The cache driver - */ - public function get_driver() - { - return $this->driver; - } - - /** - * Replaces the cache driver used by this cache service. - * - * @param phpbb_cache_driver_interface $driver The cache driver - */ - public function set_driver(phpbb_cache_driver_interface $driver) - { - $this->driver = $driver; - } - - public function __call($method, $arguments) - { - return call_user_func_array(array($this->driver, $method), $arguments); - } - - /** - * Obtain list of naughty words and build preg style replacement arrays for use by the - * calling script - */ - function obtain_word_list() - { - if (($censors = $this->driver->get('_word_censors')) === false) - { - $sql = 'SELECT word, replacement - FROM ' . WORDS_TABLE; - $result = $this->db->sql_query($sql); - - $censors = array(); - while ($row = $this->db->sql_fetchrow($result)) - { - $censors['match'][] = get_censor_preg_expression($row['word']); - $censors['replace'][] = $row['replacement']; - } - $this->db->sql_freeresult($result); - - $this->driver->put('_word_censors', $censors); - } - - return $censors; - } - - /** - * Obtain currently listed icons - */ - function obtain_icons() - { - if (($icons = $this->driver->get('_icons')) === false) - { - // Topic icons - $sql = 'SELECT * - FROM ' . ICONS_TABLE . ' - ORDER BY icons_order'; - $result = $this->db->sql_query($sql); - - $icons = array(); - while ($row = $this->db->sql_fetchrow($result)) - { - $icons[$row['icons_id']]['img'] = $row['icons_url']; - $icons[$row['icons_id']]['width'] = (int) $row['icons_width']; - $icons[$row['icons_id']]['height'] = (int) $row['icons_height']; - $icons[$row['icons_id']]['display'] = (bool) $row['display_on_posting']; - } - $this->db->sql_freeresult($result); - - $this->driver->put('_icons', $icons); - } - - return $icons; - } - - /** - * Obtain ranks - */ - function obtain_ranks() - { - if (($ranks = $this->driver->get('_ranks')) === false) - { - $sql = 'SELECT * - FROM ' . RANKS_TABLE . ' - ORDER BY rank_min DESC'; - $result = $this->db->sql_query($sql); - - $ranks = array(); - while ($row = $this->db->sql_fetchrow($result)) - { - if ($row['rank_special']) - { - $ranks['special'][$row['rank_id']] = array( - 'rank_title' => $row['rank_title'], - 'rank_image' => $row['rank_image'] - ); - } - else - { - $ranks['normal'][] = array( - 'rank_title' => $row['rank_title'], - 'rank_min' => $row['rank_min'], - 'rank_image' => $row['rank_image'] - ); - } - } - $this->db->sql_freeresult($result); - - $this->driver->put('_ranks', $ranks); - } - - return $ranks; - } - - /** - * Obtain allowed extensions - * - * @param mixed $forum_id If false then check for private messaging, if int then check for forum id. If true, then only return extension informations. - * - * @return array allowed extensions array. - */ - function obtain_attach_extensions($forum_id) - { - if (($extensions = $this->driver->get('_extensions')) === false) - { - $extensions = array( - '_allowed_post' => array(), - '_allowed_pm' => array(), - ); - - // The rule is to only allow those extensions defined. ;) - $sql = 'SELECT e.extension, g.* - FROM ' . EXTENSIONS_TABLE . ' e, ' . EXTENSION_GROUPS_TABLE . ' g - WHERE e.group_id = g.group_id - AND (g.allow_group = 1 OR g.allow_in_pm = 1)'; - $result = $this->db->sql_query($sql); - - while ($row = $this->db->sql_fetchrow($result)) - { - $extension = strtolower(trim($row['extension'])); - - $extensions[$extension] = array( - 'display_cat' => (int) $row['cat_id'], - 'download_mode' => (int) $row['download_mode'], - 'upload_icon' => trim($row['upload_icon']), - 'max_filesize' => (int) $row['max_filesize'], - 'allow_group' => $row['allow_group'], - 'allow_in_pm' => $row['allow_in_pm'], - 'group_name' => $row['group_name'], - ); - - $allowed_forums = ($row['allowed_forums']) ? unserialize(trim($row['allowed_forums'])) : array(); - - // Store allowed extensions forum wise - if ($row['allow_group']) - { - $extensions['_allowed_post'][$extension] = (!sizeof($allowed_forums)) ? 0 : $allowed_forums; - } - - if ($row['allow_in_pm']) - { - $extensions['_allowed_pm'][$extension] = 0; - } - } - $this->db->sql_freeresult($result); - - $this->driver->put('_extensions', $extensions); - } - - // Forum post - if ($forum_id === false) - { - // We are checking for private messages, therefore we only need to get the pm extensions... - $return = array('_allowed_' => array()); - - foreach ($extensions['_allowed_pm'] as $extension => $check) - { - $return['_allowed_'][$extension] = 0; - $return[$extension] = $extensions[$extension]; - } - - $extensions = $return; - } - else if ($forum_id === true) - { - return $extensions; - } - else - { - $forum_id = (int) $forum_id; - $return = array('_allowed_' => array()); - - foreach ($extensions['_allowed_post'] as $extension => $check) - { - // Check for allowed forums - if (is_array($check)) - { - $allowed = (!in_array($forum_id, $check)) ? false : true; - } - else - { - $allowed = true; - } - - if ($allowed) - { - $return['_allowed_'][$extension] = 0; - $return[$extension] = $extensions[$extension]; - } - } - - $extensions = $return; - } - - if (!isset($extensions['_allowed_'])) - { - $extensions['_allowed_'] = array(); - } - - return $extensions; - } - - /** - * Obtain active bots - */ - function obtain_bots() - { - if (($bots = $this->driver->get('_bots')) === false) - { - switch ($this->db->sql_layer) - { - case 'mssql': - case 'mssql_odbc': - case 'mssqlnative': - $sql = 'SELECT user_id, bot_agent, bot_ip - FROM ' . BOTS_TABLE . ' - WHERE bot_active = 1 - ORDER BY LEN(bot_agent) DESC'; - break; - - case 'firebird': - $sql = 'SELECT user_id, bot_agent, bot_ip - FROM ' . BOTS_TABLE . ' - WHERE bot_active = 1 - ORDER BY CHAR_LENGTH(bot_agent) DESC'; - break; - - // LENGTH supported by MySQL, IBM DB2 and Oracle for sure... - default: - $sql = 'SELECT user_id, bot_agent, bot_ip - FROM ' . BOTS_TABLE . ' - WHERE bot_active = 1 - ORDER BY LENGTH(bot_agent) DESC'; - break; - } - $result = $this->db->sql_query($sql); - - $bots = array(); - while ($row = $this->db->sql_fetchrow($result)) - { - $bots[] = $row; - } - $this->db->sql_freeresult($result); - - $this->driver->put('_bots', $bots); - } - - return $bots; - } - - /** - * Obtain cfg file data - */ - function obtain_cfg_items($style) - { - $parsed_array = $this->driver->get('_cfg_' . $style['style_path']); - - if ($parsed_array === false) - { - $parsed_array = array(); - } - - $filename = $this->phpbb_root_path . 'styles/' . $style['style_path'] . '/style.cfg'; - - if (!file_exists($filename)) - { - return $parsed_array; - } - - if (!isset($parsed_array['filetime']) || (($this->config['load_tplcompile'] && @filemtime($filename) > $parsed_array['filetime']))) - { - // Re-parse cfg file - $parsed_array = parse_cfg_file($filename); - $parsed_array['filetime'] = @filemtime($filename); - - $this->driver->put('_cfg_' . $style['style_path'], $parsed_array); - } - - return $parsed_array; - } - - /** - * Obtain disallowed usernames - */ - function obtain_disallowed_usernames() - { - if (($usernames = $this->driver->get('_disallowed_usernames')) === false) - { - $sql = 'SELECT disallow_username - FROM ' . DISALLOW_TABLE; - $result = $this->db->sql_query($sql); - - $usernames = array(); - while ($row = $this->db->sql_fetchrow($result)) - { - $usernames[] = str_replace('%', '.*?', preg_quote(utf8_clean_string($row['disallow_username']), '#')); - } - $this->db->sql_freeresult($result); - - $this->driver->put('_disallowed_usernames', $usernames); - } - - return $usernames; - } -} diff --git a/phpBB/includes/class_loader.php b/phpBB/includes/class_loader.php deleted file mode 100644 index 02a2d584dc..0000000000 --- a/phpBB/includes/class_loader.php +++ /dev/null @@ -1,170 +0,0 @@ -path. - * This map is stored in cache and looked up if the cache is available. - * - * @var array - */ - private $cached_paths = array(); - - /** - * Creates a new phpbb_class_loader, which loads files with the given - * file extension from the given path. - * - * @param string $prefix Required class name prefix for files to be loaded - * @param string $path Directory to load files from - * @param string $php_ext The file extension for PHP files - * @param phpbb_cache_driver_interface $cache An implementation of the phpBB cache interface. - */ - public function __construct($prefix, $path, $php_ext = 'php', phpbb_cache_driver_interface $cache = null) - { - $this->prefix = $prefix; - $this->path = $path; - $this->php_ext = $php_ext; - - $this->set_cache($cache); - } - - /** - * Provide the class loader with a cache to store paths. If set to null, the - * the class loader will resolve paths by checking for the existance of every - * directory in the class name every time. - * - * @param phpbb_cache_driver_interface $cache An implementation of the phpBB cache interface. - */ - public function set_cache(phpbb_cache_driver_interface $cache = null) - { - if ($cache) - { - $this->cached_paths = $cache->get('class_loader_' . $this->prefix); - - if ($this->cached_paths === false) - { - $this->cached_paths = array(); - } - } - - $this->cache = $cache; - } - - /** - * Registers the class loader as an autoloader using SPL. - */ - public function register() - { - spl_autoload_register(array($this, 'load_class')); - } - - /** - * Removes the class loader from the SPL autoloader stack. - */ - public function unregister() - { - spl_autoload_unregister(array($this, 'load_class')); - } - - /** - * Resolves a phpBB class name to a relative path which can be included. - * - * @param string $class The class name to resolve, must have a phpbb_ - * prefix - * @return string|bool A relative path to the file containing the - * class or false if looking it up failed. - */ - public function resolve_path($class) - { - if (isset($this->cached_paths[$class])) - { - return $this->path . $this->cached_paths[$class] . '.' . $this->php_ext; - } - - if (!preg_match('/^' . $this->prefix . '[a-zA-Z0-9_]+$/', $class)) - { - return false; - } - - $parts = explode('_', substr($class, strlen($this->prefix))); - - $dirs = ''; - - for ($i = 0, $n = sizeof($parts); $i < $n && is_dir($this->path . $dirs . $parts[$i]); $i++) - { - $dirs .= $parts[$i] . '/'; - } - - // no file name left => use last dir name as file name - if ($i == sizeof($parts)) - { - $parts[] = $parts[$i - 1]; - } - - $relative_path = $dirs . implode(array_slice($parts, $i, sizeof($parts) - $i), '_'); - - if (!file_exists($this->path . $relative_path . '.' . $this->php_ext)) - { - return false; - } - - if ($this->cache) - { - $this->cached_paths[$class] = $relative_path; - $this->cache->put('class_loader_' . $this->prefix, $this->cached_paths); - } - - return $this->path . $relative_path . '.' . $this->php_ext; - } - - /** - * Resolves a class name to a path and then includes it. - * - * @param string $class The class name which is being loaded. - */ - public function load_class($class) - { - if (substr($class, 0, strlen($this->prefix)) === $this->prefix) - { - $path = $this->resolve_path($class); - - if ($path) - { - require $path; - } - } - } -} diff --git a/phpBB/includes/config/config.php b/phpBB/includes/config/config.php deleted file mode 100644 index 4b533dd55c..0000000000 --- a/phpBB/includes/config/config.php +++ /dev/null @@ -1,170 +0,0 @@ - string) - */ - protected $config; - - /** - * Creates a configuration container with a default set of values - * - * @param array(string => string) $config The configuration data. - */ - public function __construct(array $config) - { - $this->config = $config; - } - - /** - * Retrieves an ArrayIterator over the configuration values. - * - * @return ArrayIterator An iterator over all config data - */ - public function getIterator() - { - return new ArrayIterator($this->config); - } - - /** - * Checks if the specified config value exists. - * - * @param string $key The configuration option's name. - * @return bool Whether the configuration option exists. - */ - public function offsetExists($key) - { - return isset($this->config[$key]); - } - - /** - * Retrieves a configuration value. - * - * @param string $key The configuration option's name. - * @return string The configuration value - */ - public function offsetGet($key) - { - return (isset($this->config[$key])) ? $this->config[$key] : ''; - } - - /** - * Temporarily overwrites the value of a configuration variable. - * - * The configuration change will not persist. It will be lost - * after the request. - * - * @param string $key The configuration option's name. - * @param string $value The temporary value. - */ - public function offsetSet($key, $value) - { - $this->config[$key] = $value; - } - - /** - * Called when deleting a configuration value directly, triggers an error. - * - * @param string $key The configuration option's name. - */ - public function offsetUnset($key) - { - trigger_error('Config values have to be deleted explicitly with the phpbb_config::delete($key) method.', E_USER_ERROR); - } - - /** - * Retrieves the number of configuration options currently set. - * - * @return int Number of config options - */ - public function count() - { - return count($this->config); - } - - /** - * Removes a configuration option - * - * @param String $key The configuration option's name - * @param bool $use_cache Whether this variable should be cached or if it - * changes too frequently to be efficiently cached - * @return null - */ - public function delete($key, $use_cache = true) - { - unset($this->config[$key]); - } - - /** - * Sets a configuration option's value - * - * @param string $key The configuration option's name - * @param string $value New configuration value - * @param bool $use_cache Whether this variable should be cached or if it - * changes too frequently to be efficiently cached. - */ - public function set($key, $value, $use_cache = true) - { - $this->config[$key] = $value; - } - - /** - * Sets a configuration option's value only if the old_value matches the - * current configuration value or the configuration value does not exist yet. - * - * @param string $key The configuration option's name - * @param string $old_value Current configuration value - * @param string $new_value New configuration value - * @param bool $use_cache Whether this variable should be cached or if it - * changes too frequently to be efficiently cached. - * @return bool True if the value was changed, false otherwise. - */ - public function set_atomic($key, $old_value, $new_value, $use_cache = true) - { - if (!isset($this->config[$key]) || $this->config[$key] == $old_value) - { - $this->config[$key] = $new_value; - return true; - } - return false; - } - - /** - * Increments an integer configuration value. - * - * @param string $key The configuration option's name - * @param int $increment Amount to increment by - * @param bool $use_cache Whether this variable should be cached or if it - * changes too frequently to be efficiently cached. - */ - function increment($key, $increment, $use_cache = true) - { - if (!isset($this->config[$key])) - { - $this->config[$key] = 0; - } - - $this->config[$key] += $increment; - } -} diff --git a/phpBB/includes/config/db.php b/phpBB/includes/config/db.php deleted file mode 100644 index b18369a479..0000000000 --- a/phpBB/includes/config/db.php +++ /dev/null @@ -1,207 +0,0 @@ -db = $db; - $this->cache = $cache; - $this->table = $table; - - if (($config = $cache->get('config')) !== false) - { - $sql = 'SELECT config_name, config_value - FROM ' . $this->table . ' - WHERE is_dynamic = 1'; - $result = $this->db->sql_query($sql); - - while ($row = $this->db->sql_fetchrow($result)) - { - $config[$row['config_name']] = $row['config_value']; - } - $this->db->sql_freeresult($result); - } - else - { - $config = $cached_config = array(); - - $sql = 'SELECT config_name, config_value, is_dynamic - FROM ' . $this->table; - $result = $this->db->sql_query($sql); - - while ($row = $this->db->sql_fetchrow($result)) - { - if (!$row['is_dynamic']) - { - $cached_config[$row['config_name']] = $row['config_value']; - } - - $config[$row['config_name']] = $row['config_value']; - } - $this->db->sql_freeresult($result); - - $cache->put('config', $cached_config); - } - - parent::__construct($config); - } - - /** - * Removes a configuration option - * - * @param String $key The configuration option's name - * @param bool $use_cache Whether this variable should be cached or if it - * changes too frequently to be efficiently cached - * @return null - */ - public function delete($key, $use_cache = true) - { - $sql = 'DELETE FROM ' . $this->table . " - WHERE config_name = '" . $this->db->sql_escape($key) . "'"; - $this->db->sql_query($sql); - - unset($this->config[$key]); - - if ($use_cache) - { - $this->cache->destroy('config'); - } - } - - /** - * Sets a configuration option's value - * - * @param string $key The configuration option's name - * @param string $value New configuration value - * @param bool $use_cache Whether this variable should be cached or if it - * changes too frequently to be efficiently cached. - */ - public function set($key, $value, $use_cache = true) - { - $this->set_atomic($key, false, $value, $use_cache); - } - - /** - * Sets a configuration option's value only if the old_value matches the - * current configuration value or the configuration value does not exist yet. - * - * @param string $key The configuration option's name - * @param mixed $old_value Current configuration value or false to ignore - * the old value - * @param string $new_value New configuration value - * @param bool $use_cache Whether this variable should be cached or if it - * changes too frequently to be efficiently cached - * @return bool True if the value was changed, false otherwise - */ - public function set_atomic($key, $old_value, $new_value, $use_cache = true) - { - $sql = 'UPDATE ' . $this->table . " - SET config_value = '" . $this->db->sql_escape($new_value) . "' - WHERE config_name = '" . $this->db->sql_escape($key) . "'"; - - if ($old_value !== false) - { - $sql .= " AND config_value = '" . $this->db->sql_escape($old_value) . "'"; - } - - $result = $this->db->sql_query($sql); - - if (!$this->db->sql_affectedrows($result) && isset($this->config[$key])) - { - return false; - } - - if (!isset($this->config[$key])) - { - $sql = 'INSERT INTO ' . $this->table . ' ' . $this->db->sql_build_array('INSERT', array( - 'config_name' => $key, - 'config_value' => $new_value, - 'is_dynamic' => ($use_cache) ? 0 : 1)); - $this->db->sql_query($sql); - } - - if ($use_cache) - { - $this->cache->destroy('config'); - } - - $this->config[$key] = $new_value; - return true; - } - - /** - * Increments an integer config value directly in the database. - * - * Using this method instead of setting the new value directly avoids race - * conditions and unlike set_atomic it cannot fail. - * - * @param string $key The configuration option's name - * @param int $increment Amount to increment by - * @param bool $use_cache Whether this variable should be cached or if it - * changes too frequently to be efficiently cached. - */ - function increment($key, $increment, $use_cache = true) - { - if (!isset($this->config[$key])) - { - $this->set($key, '0', $use_cache); - } - - $sql_update = $this->db->cast_expr_to_string($this->db->cast_expr_to_bigint('config_value') . ' + ' . (int) $increment); - - $this->db->sql_query('UPDATE ' . $this->table . ' - SET config_value = ' . $sql_update . " - WHERE config_name = '" . $this->db->sql_escape($key) . "'"); - - if ($use_cache) - { - $this->cache->destroy('config'); - } - - $this->config[$key] += $increment; - } -} diff --git a/phpBB/includes/config/db_text.php b/phpBB/includes/config/db_text.php deleted file mode 100644 index b365cb5c77..0000000000 --- a/phpBB/includes/config/db_text.php +++ /dev/null @@ -1,163 +0,0 @@ -db = $db; - $this->table = $this->db->sql_escape($table); - } - - /** - * Sets the configuration option with the name $key to $value. - * - * @param string $key The configuration option's name - * @param string $value New configuration value - * - * @return null - */ - public function set($key, $value) - { - $this->set_array(array($key => $value)); - } - - /** - * Gets the configuration value for the name $key. - * - * @param string $key The configuration option's name - * - * @return string|null String result on success - * null if there is no such option - */ - public function get($key) - { - $map = $this->get_array(array($key)); - - return isset($map[$key]) ? $map[$key] : null; - } - - /** - * Removes the configuration option with the name $key. - * - * @param string $key The configuration option's name - * - * @return null - */ - public function delete($key) - { - $this->delete_array(array($key)); - } - - /** - * Mass set configuration options: Receives an associative array, - * treats array keys as configuration option names and associated - * array values as their configuration option values. - * - * @param array $map Map from configuration names to values - * - * @return null - */ - public function set_array(array $map) - { - $this->db->sql_transaction('begin'); - - foreach ($map as $key => $value) - { - $sql = 'UPDATE ' . $this->table . " - SET config_value = '" . $this->db->sql_escape($value) . "' - WHERE config_name = '" . $this->db->sql_escape($key) . "'"; - $result = $this->db->sql_query($sql); - - if (!$this->db->sql_affectedrows($result)) - { - $sql = 'INSERT INTO ' . $this->table . ' ' . $this->db->sql_build_array('INSERT', array( - 'config_name' => $key, - 'config_value' => $value, - )); - $this->db->sql_query($sql); - } - } - - $this->db->sql_transaction('commit'); - } - - /** - * Mass get configuration options: Receives a set of configuration - * option names and returns the result as a key => value map where - * array keys are configuration option names and array values are - * associated config option values. - * - * @param array $keys Set of configuration option names - * - * @return array Map from configuration names to values - */ - public function get_array(array $keys) - { - $sql = 'SELECT * - FROM ' . $this->table . ' - WHERE ' . $this->db->sql_in_set('config_name', $keys, false, true); - $result = $this->db->sql_query($sql); - - $map = array(); - while ($row = $this->db->sql_fetchrow($result)) - { - $map[$row['config_name']] = $row['config_value']; - } - $this->db->sql_freeresult($result); - - return $map; - } - - /** - * Mass delete configuration options. - * - * @param array $keys Set of configuration option names - * - * @return null - */ - public function delete_array(array $keys) - { - $sql = 'DELETE - FROM ' . $this->table . ' - WHERE ' . $this->db->sql_in_set('config_name', $keys, false, true); - $result = $this->db->sql_query($sql); - } -} diff --git a/phpBB/includes/content_visibility.php b/phpBB/includes/content_visibility.php deleted file mode 100644 index 4ad5f6793e..0000000000 --- a/phpBB/includes/content_visibility.php +++ /dev/null @@ -1,651 +0,0 @@ -auth = $auth; - $this->db = $db; - $this->user = $user; - $this->phpbb_root_path = $phpbb_root_path; - $this->php_ext = $php_ext; - $this->forums_table = $forums_table; - $this->posts_table = $posts_table; - $this->topics_table = $topics_table; - $this->users_table = $users_table; - } - - /** - * Can the current logged-in user soft-delete posts? - * - * @param $forum_id int Forum ID whose permissions to check - * @param $poster_id int Poster ID of the post in question - * @param $post_locked bool Is the post locked? - * @return bool - */ - public function can_soft_delete($forum_id, $poster_id, $post_locked) - { - if ($this->auth->acl_get('m_softdelete', $forum_id)) - { - return true; - } - else if ($this->auth->acl_get('f_softdelete', $forum_id) && $poster_id == $this->user->data['user_id'] && !$post_locked) - { - return true; - } - - return false; - } - - /** - * Get the topics post count or the forums post/topic count based on permissions - * - * @param $mode string One of topic_posts, forum_posts or forum_topics - * @param $data array Array with the topic/forum data to calculate from - * @param $forum_id int The forum id is used for permission checks - * @return int Number of posts/topics the user can see in the topic/forum - */ - public function get_count($mode, $data, $forum_id) - { - if (!$this->auth->acl_get('m_approve', $forum_id)) - { - return (int) $data[$mode . '_approved']; - } - - return (int) $data[$mode . '_approved'] + (int) $data[$mode . '_unapproved'] + (int) $data[$mode . '_softdeleted']; - } - - /** - * Create topic/post visibility SQL for a given forum ID - * - * Note: Read permissions are not checked. - * - * @param $mode string Either "topic" or "post" - * @param $forum_id int The forum id is used for permission checks - * @param $table_alias string Table alias to prefix in SQL queries - * @return string The appropriate combination SQL logic for topic/post_visibility - */ - public function get_visibility_sql($mode, $forum_id, $table_alias = '') - { - if ($this->auth->acl_get('m_approve', $forum_id)) - { - return '1 = 1'; - } - - return $table_alias . $mode . '_visibility = ' . ITEM_APPROVED; - } - - /** - * Create topic/post visibility SQL for a set of forums - * - * Note: Read permissions are not checked. Forums without read permissions - * should not be in $forum_ids - * - * @param $mode string Either "topic" or "post" - * @param $forum_ids array Array of forum ids which the posts/topics are limited to - * @param $table_alias string Table alias to prefix in SQL queries - * @return string The appropriate combination SQL logic for topic/post_visibility - */ - public function get_forums_visibility_sql($mode, $forum_ids = array(), $table_alias = '') - { - $where_sql = '('; - - $approve_forums = array_intersect($forum_ids, array_keys($this->auth->acl_getf('m_approve', true))); - - if (sizeof($approve_forums)) - { - // Remove moderator forums from the rest - $forum_ids = array_diff($forum_ids, $approve_forums); - - if (!sizeof($forum_ids)) - { - // The user can see all posts/topics in all specified forums - return $this->db->sql_in_set($table_alias . 'forum_id', $approve_forums); - } - else - { - // Moderator can view all posts/topics in some forums - $where_sql .= $this->db->sql_in_set($table_alias . 'forum_id', $approve_forums) . ' OR '; - } - } - else - { - // The user is just a normal user - return $table_alias . $mode . '_visibility = ' . ITEM_APPROVED . ' - AND ' . $this->db->sql_in_set($table_alias . 'forum_id', $forum_ids, false, true); - } - - $where_sql .= '(' . $table_alias . $mode . '_visibility = ' . ITEM_APPROVED . ' - AND ' . $this->db->sql_in_set($table_alias . 'forum_id', $forum_ids) . '))'; - - return $where_sql; - } - - /** - * Create topic/post visibility SQL for all forums on the board - * - * Note: Read permissions are not checked. Forums without read permissions - * should be in $exclude_forum_ids - * - * @param $mode string Either "topic" or "post" - * @param $exclude_forum_ids array Array of forum ids which are excluded - * @param $table_alias string Table alias to prefix in SQL queries - * @return string The appropriate combination SQL logic for topic/post_visibility - */ - public function get_global_visibility_sql($mode, $exclude_forum_ids = array(), $table_alias = '') - { - $where_sqls = array(); - - $approve_forums = array_diff(array_keys($this->auth->acl_getf('m_approve', true)), $exclude_forum_ids); - - if (sizeof($exclude_forum_ids)) - { - $where_sqls[] = '(' . $this->db->sql_in_set($table_alias . 'forum_id', $exclude_forum_ids, true) . ' - AND ' . $table_alias . $mode . '_visibility = ' . ITEM_APPROVED . ')'; - } - else - { - $where_sqls[] = $table_alias . $mode . '_visibility = ' . ITEM_APPROVED; - } - - if (sizeof($approve_forums)) - { - $where_sqls[] = $this->db->sql_in_set($table_alias . 'forum_id', $approve_forums); - return '(' . implode(' OR ', $where_sqls) . ')'; - } - - // There is only one element, so we just return that one - return $where_sqls[0]; - } - - /** - * Change visibility status of one post or all posts of a topic - * - * @param $visibility int Element of {ITEM_APPROVED, ITEM_DELETED} - * @param $post_id mixed Post ID or array of post IDs to act on, - * if it is empty, all posts of topic_id will be modified - * @param $topic_id int Topic where $post_id is found - * @param $forum_id int Forum where $topic_id is found - * @param $user_id int User performing the action - * @param $time int Timestamp when the action is performed - * @param $reason string Reason why the visibilty was changed. - * @param $is_starter bool Is this the first post of the topic changed? - * @param $is_latest bool Is this the last post of the topic changed? - * @param $limit_visibility mixed Limit updating per topic_id to a certain visibility - * @param $limit_delete_time mixed Limit updating per topic_id to a certain deletion time - * @return array Changed post data, empty array if an error occured. - */ - public function set_post_visibility($visibility, $post_id, $topic_id, $forum_id, $user_id, $time, $reason, $is_starter, $is_latest, $limit_visibility = false, $limit_delete_time = false) - { - if (!in_array($visibility, array(ITEM_APPROVED, ITEM_DELETED))) - { - return array(); - } - - if ($post_id) - { - if (is_array($post_id)) - { - $where_sql = $this->db->sql_in_set('post_id', array_map('intval', $post_id)); - } - else - { - $where_sql = 'post_id = ' . (int) $post_id; - } - $where_sql .= ' AND topic_id = ' . (int) $topic_id; - } - else - { - $where_sql = 'topic_id = ' . (int) $topic_id; - - // Limit the posts to a certain visibility and deletion time - // This allows us to only restore posts, that were approved - // when the topic got soft deleted. So previous soft deleted - // and unapproved posts are still soft deleted/unapproved - if ($limit_visibility !== false) - { - $where_sql .= ' AND post_visibility = ' . (int) $limit_visibility; - } - - if ($limit_delete_time !== false) - { - $where_sql .= ' AND post_delete_time = ' . (int) $limit_delete_time; - } - } - - $sql = 'SELECT poster_id, post_id, post_postcount, post_visibility - FROM ' . $this->posts_table . ' - WHERE ' . $where_sql; - $result = $this->db->sql_query($sql); - - $post_ids = $poster_postcounts = $postcounts = $postcount_visibility = array(); - while ($row = $this->db->sql_fetchrow($result)) - { - $post_ids[] = (int) $row['post_id']; - - if ($row['post_visibility'] != $visibility) - { - if ($row['post_postcount'] && !isset($poster_postcounts[(int) $row['poster_id']])) - { - $poster_postcounts[(int) $row['poster_id']] = 1; - } - else if ($row['post_postcount']) - { - $poster_postcounts[(int) $row['poster_id']]++; - } - - if (!isset($postcount_visibility[$row['post_visibility']])) - { - $postcount_visibility[$row['post_visibility']] = 1; - } - else - { - $postcount_visibility[$row['post_visibility']]++; - } - } - } - $this->db->sql_freeresult($result); - - if (empty($post_ids)) - { - return array(); - } - - $data = array( - 'post_visibility' => (int) $visibility, - 'post_delete_user' => (int) $user_id, - 'post_delete_time' => ((int) $time) ?: time(), - 'post_delete_reason' => truncate_string($reason, 255, 255, false), - ); - - $sql = 'UPDATE ' . $this->posts_table . ' - SET ' . $this->db->sql_build_array('UPDATE', $data) . ' - WHERE ' . $this->db->sql_in_set('post_id', $post_ids); - $this->db->sql_query($sql); - - // Group the authors by post count, to reduce the number of queries - foreach ($poster_postcounts as $poster_id => $num_posts) - { - $postcounts[$num_posts][] = $poster_id; - } - - // Update users postcounts - foreach ($postcounts as $num_posts => $poster_ids) - { - if ($visibility == ITEM_DELETED) - { - $sql = 'UPDATE ' . $this->users_table . ' - SET user_posts = 0 - WHERE ' . $this->db->sql_in_set('user_id', $poster_ids) . ' - AND user_posts < ' . $num_posts; - $this->db->sql_query($sql); - - $sql = 'UPDATE ' . $this->users_table . ' - SET user_posts = user_posts - ' . $num_posts . ' - WHERE ' . $this->db->sql_in_set('user_id', $poster_ids) . ' - AND user_posts >= ' . $num_posts; - $this->db->sql_query($sql); - } - else - { - $sql = 'UPDATE ' . $this->users_table . ' - SET user_posts = user_posts + ' . $num_posts . ' - WHERE ' . $this->db->sql_in_set('user_id', $poster_ids); - $this->db->sql_query($sql); - } - } - - $update_topic_postcount = true; - - // Sync the first/last topic information if needed - if (!$is_starter && $is_latest) - { - // update_post_information can only update the last post info ... - if ($topic_id) - { - update_post_information('topic', $topic_id, false); - } - if ($forum_id) - { - update_post_information('forum', $forum_id, false); - } - } - else if ($is_starter && $topic_id) - { - if (!function_exists('sync')) - { - include($this->phpbb_root_path . 'includes/functions_admin.' . $this->php_ext); - } - - // ... so we need to use sync, if the first post is changed. - // The forum is resynced recursive by sync() itself. - sync('topic', 'topic_id', $topic_id, true); - - // sync recalculates the topic replies and forum posts by itself, so we don't do that. - $update_topic_postcount = false; - } - - // Update the topic's reply count and the forum's post count - if ($update_topic_postcount) - { - $cur_posts = $cur_unapproved_posts = $cur_softdeleted_posts = 0; - foreach ($postcount_visibility as $post_visibility => $visibility_posts) - { - // We need to substract the posts from the counters ... - if ($post_visibility == ITEM_APPROVED) - { - $cur_posts += $visibility_posts; - } - else if ($post_visibility == ITEM_UNAPPROVED) - { - $cur_unapproved_posts += $visibility_posts; - } - else if ($post_visibility == ITEM_DELETED) - { - $cur_softdeleted_posts += $visibility_posts; - } - } - - $sql_ary = array(); - if ($visibility == ITEM_DELETED) - { - if ($cur_posts) - { - $sql_ary['posts_approved'] = ' - ' . $cur_posts; - } - if ($cur_unapproved_posts) - { - $sql_ary['posts_unapproved'] = ' - ' . $cur_unapproved_posts; - } - if ($cur_posts + $cur_unapproved_posts) - { - $sql_ary['posts_softdeleted'] = ' + ' . ($cur_posts + $cur_unapproved_posts); - } - } - else - { - if ($cur_unapproved_posts) - { - $sql_ary['posts_unapproved'] = ' - ' . $cur_unapproved_posts; - } - if ($cur_softdeleted_posts) - { - $sql_ary['posts_softdeleted'] = ' - ' . $cur_softdeleted_posts; - } - if ($cur_softdeleted_posts + $cur_unapproved_posts) - { - $sql_ary['posts_approved'] = ' + ' . ($cur_softdeleted_posts + $cur_unapproved_posts); - } - } - - if (sizeof($sql_ary)) - { - $topic_sql = $forum_sql = array(); - - foreach ($sql_ary as $field => $value_change) - { - $topic_sql[] = 'topic_' . $field . ' = topic_' . $field . $value_change; - $forum_sql[] = 'forum_' . $field . ' = forum_' . $field . $value_change; - } - - // Update the number for replies and posts - $sql = 'UPDATE ' . $this->topics_table . ' - SET ' . implode(', ', $topic_sql) . ' - WHERE topic_id = ' . (int) $topic_id; - $this->db->sql_query($sql); - - $sql = 'UPDATE ' . $this->forums_table . ' - SET ' . implode(', ', $forum_sql) . ' - WHERE forum_id = ' . (int) $forum_id; - $this->db->sql_query($sql); - } - } - - return $data; - } - - /** - * Set topic visibility - * - * Allows approving (which is akin to undeleting/restore) or soft deleting an entire topic. - * Calls set_post_visibility as needed. - * - * Note: By default, when a soft deleted topic is restored. Only posts that - * were approved at the time of soft deleting, are being restored. - * Same applies to soft deleting. Only approved posts will be marked - * as soft deleted. - * If you want to update all posts, use the force option. - * - * @param $visibility int Element of {ITEM_APPROVED, ITEM_DELETED} - * @param $topic_id mixed Topic ID to act on - * @param $forum_id int Forum where $topic_id is found - * @param $user_id int User performing the action - * @param $time int Timestamp when the action is performed - * @param $reason string Reason why the visibilty was changed. - * @param $force_update_all bool Force to update all posts within the topic - * @return array Changed topic data, empty array if an error occured. - */ - public function set_topic_visibility($visibility, $topic_id, $forum_id, $user_id, $time, $reason, $force_update_all = false) - { - if (!in_array($visibility, array(ITEM_APPROVED, ITEM_DELETED))) - { - return array(); - } - - if (!$force_update_all) - { - $sql = 'SELECT topic_visibility, topic_delete_time - FROM ' . $this->topics_table . ' - WHERE topic_id = ' . (int) $topic_id; - $result = $this->db->sql_query($sql); - $original_topic_data = $this->db->sql_fetchrow($result); - $this->db->sql_freeresult($result); - - if (!$original_topic_data) - { - // The topic does not exist... - return array(); - } - } - - // Note, we do not set a reason for the posts, just for the topic - $data = array( - 'topic_visibility' => (int) $visibility, - 'topic_delete_user' => (int) $user_id, - 'topic_delete_time' => ((int) $time) ?: time(), - 'topic_delete_reason' => truncate_string($reason, 255, 255, false), - ); - - $sql = 'UPDATE ' . $this->topics_table . ' - SET ' . $this->db->sql_build_array('UPDATE', $data) . ' - WHERE topic_id = ' . (int) $topic_id; - $this->db->sql_query($sql); - - if (!$this->db->sql_affectedrows()) - { - return array(); - } - - if (!$force_update_all && $original_topic_data['topic_delete_time'] && $original_topic_data['topic_visibility'] == ITEM_DELETED && $visibility == ITEM_APPROVED) - { - // If we're restoring a topic we only restore posts, that were soft deleted through the topic soft deletion. - self::set_post_visibility($visibility, false, $topic_id, $forum_id, $user_id, $time, '', true, true, $original_topic_data['topic_visibility'], $original_topic_data['topic_delete_time']); - } - else if (!$force_update_all && $original_topic_data['topic_visibility'] == ITEM_APPROVED && $visibility == ITEM_DELETED) - { - // If we're soft deleting a topic we only approved posts are soft deleted. - self::set_post_visibility($visibility, false, $topic_id, $forum_id, $user_id, $time, '', true, true, $original_topic_data['topic_visibility']); - } - else - { - self::set_post_visibility($visibility, false, $topic_id, $forum_id, $user_id, $time, '', true, true); - } - - return $data; - } - - /** - * Add post to topic and forum statistics - * - * @param $data array Contains information from the topics table about given topic - * @param $sql_data array Populated with the SQL changes, may be empty at call time - * @return void - */ - public function add_post_to_statistic($data, &$sql_data) - { - $sql_data[$this->topics_table] = (($sql_data[$this->topics_table]) ? $sql_data[$this->topics_table] . ', ' : '') . 'topic_posts_approved = topic_posts_approved + 1'; - - $sql_data[$this->forums_table] = (($sql_data[$this->forums_table]) ? $sql_data[$this->forums_table] . ', ' : '') . 'forum_posts_approved = forum_posts_approved + 1'; - - if ($data['post_postcount']) - { - $sql_data[$this->users_table] = (($sql_data[$this->users_table]) ? $sql_data[$this->users_table] . ', ' : '') . 'user_posts = user_posts + 1'; - } - - set_config_count('num_posts', 1, true); - } - - /** - * Remove post from topic and forum statistics - * - * @param $data array Contains information from the topics table about given topic - * @param $sql_data array Populated with the SQL changes, may be empty at call time - * @return void - */ - public function remove_post_from_statistic($data, &$sql_data) - { - $sql_data[$this->topics_table] = ((!empty($sql_data[$this->topics_table])) ? $sql_data[$this->topics_table] . ', ' : '') . 'topic_posts_approved = topic_posts_approved - 1'; - $sql_data[$this->forums_table] = ((!empty($sql_data[$this->forums_table])) ? $sql_data[$this->forums_table] . ', ' : '') . 'forum_posts_approved = forum_posts_approved - 1'; - - if ($data['post_postcount']) - { - $sql_data[$this->users_table] = ((!empty($sql_data[$this->users_table])) ? $sql_data[$this->users_table] . ', ' : '') . 'user_posts = user_posts - 1'; - } - - set_config_count('num_posts', -1, true); - } - - /** - * Remove topic from forum statistics - * - * @param $topic_id int The topic to act on - * @param $forum_id int Forum where the topic is found - * @param $topic_row array Contains information from the topic, may be empty at call time - * @param $sql_data array Populated with the SQL changes, may be empty at call time - * @return void - */ - public function remove_topic_from_statistic($topic_id, $forum_id, &$topic_row, &$sql_data) - { - // Do we need to grab some topic informations? - if (!sizeof($topic_row)) - { - $sql = 'SELECT topic_type, topic_posts_approved, topic_posts_unapproved, topic_posts_softdeleted, topic_visibility - FROM ' . $this->topics_table . ' - WHERE topic_id = ' . (int) $topic_id; - $result = $this->db->sql_query($sql); - $topic_row = $this->db->sql_fetchrow($result); - $this->db->sql_freeresult($result); - } - - // If this is an edited topic or the first post the topic gets completely disapproved later on... - $sql_data[$this->forums_table] = (($sql_data[$this->forums_table]) ? $sql_data[$this->forums_table] . ', ' : '') . 'forum_topics_approved = forum_topics_approved - 1'; - $sql_data[$this->forums_table] .= ', forum_posts_approved = forum_posts_approved - ' . $topic_row['topic_posts_approved']; - $sql_data[$this->forums_table] .= ', forum_posts_unapproved = forum_posts_unapproved - ' . $topic_row['topic_posts_unapproved']; - $sql_data[$this->forums_table] .= ', forum_posts_softdeleted = forum_posts_softdeleted - ' . $topic_row['topic_posts_softdeleted']; - - set_config_count('num_topics', -1, true); - set_config_count('num_posts', $topic_row['topic_posts_approved'] * (-1), true); - - // Get user post count information - $sql = 'SELECT poster_id, COUNT(post_id) AS num_posts - FROM ' . $this->posts_table . ' - WHERE topic_id = ' . (int) $topic_id . ' - AND post_postcount = 1 - AND post_visibility = ' . ITEM_APPROVED . ' - GROUP BY poster_id'; - $result = $this->db->sql_query($sql); - - $postcounts = array(); - while ($row = $this->db->sql_fetchrow($result)) - { - $postcounts[(int) $row['num_posts']][] = (int) $row['poster_id']; - } - $this->db->sql_freeresult($result); - - // Decrement users post count - foreach ($postcounts as $num_posts => $poster_ids) - { - $sql = 'UPDATE ' . $this->users_table . ' - SET user_posts = 0 - WHERE user_posts < ' . $num_posts . ' - AND ' . $this->db->sql_in_set('user_id', $poster_ids); - $this->db->sql_query($sql); - - $sql = 'UPDATE ' . $this->users_table . ' - SET user_posts = user_posts - ' . $num_posts . ' - WHERE user_posts >= ' . $num_posts . ' - AND ' . $this->db->sql_in_set('user_id', $poster_ids); - $this->db->sql_query($sql); - } - } -} diff --git a/phpBB/includes/controller/exception.php b/phpBB/includes/controller/exception.php deleted file mode 100644 index faa8b6b584..0000000000 --- a/phpBB/includes/controller/exception.php +++ /dev/null @@ -1,24 +0,0 @@ -template = $template; - $this->user = $user; - $this->phpbb_root_path = $phpbb_root_path; - $this->php_ext = $php_ext; - } - - /** - * Automate setting up the page and creating the response object. - * - * @param string $handle The template handle to render - * @param string $page_title The title of the page to output - * @param int $status_code The status code to be sent to the page header - * @return Response object containing rendered page - */ - public function render($template_file, $page_title = '', $status_code = 200) - { - page_header($page_title); - - $this->template->set_filenames(array( - 'body' => $template_file, - )); - - page_footer(true, false, false); - - return new Response($this->template->assign_display('body'), $status_code); - } - - /** - * Generate a URL - * - * @param string $route The route to travel - * @param mixed $params String or array of additional url parameters - * @param bool $is_amp Is url using & (true) or & (false) - * @param string $session_id Possibility to use a custom session id instead of the global one - * @return string The URL already passed through append_sid() - */ - public function url($route, $params = false, $is_amp = true, $session_id = false) - { - $route_params = ''; - if (($route_delim = strpos($route, '?')) !== false) - { - $route_params = substr($route, $route_delim); - $route = substr($route, 0, $route_delim); - } - - if (is_array($params) && !empty($params)) - { - $params = array_merge(array( - 'controller' => $route, - ), $params); - } - else if (is_string($params) && $params) - { - $params = 'controller=' . $route . (($is_amp) ? '&' : '&') . $params; - } - else - { - $params = array('controller' => $route); - } - - return append_sid($this->phpbb_root_path . 'app.' . $this->php_ext . $route_params, $params, $is_amp, $session_id); - } - - /** - * Output an error, effectively the same thing as trigger_error - * - * @param string $message The error message - * @param string $code The error code (e.g. 404, 500, 503, etc.) - * @return Response A Reponse instance - */ - public function error($message, $code = 500) - { - $this->template->assign_vars(array( - 'MESSAGE_TEXT' => $message, - 'MESSAGE_TITLE' => $this->user->lang('INFORMATION'), - )); - - return $this->render('message_body.html', $this->user->lang('INFORMATION'), $code); - } -} diff --git a/phpBB/includes/controller/provider.php b/phpBB/includes/controller/provider.php deleted file mode 100644 index b2a5b9f6b2..0000000000 --- a/phpBB/includes/controller/provider.php +++ /dev/null @@ -1,82 +0,0 @@ -routing_paths = $routing_paths; - } - - /** - * Locate paths containing routing files - * This sets an internal property but does not return the paths. - * - * @return The current instance of this object for method chaining - */ - public function import_paths_from_finder(phpbb_extension_finder $finder) - { - // We hardcode the path to the core config directory - // because the finder cannot find it - $this->routing_paths = array_merge(array('config'), array_map('dirname', array_keys($finder - ->directory('config') - ->prefix('routing') - ->suffix('yml') - ->find() - ))); - - return $this; - } - - /** - * Get a list of controllers and return it - * - * @param string $base_path Base path to prepend to file paths - * @return array Array of controllers and their route information - */ - public function find($base_path = '') - { - $routes = new RouteCollection; - foreach ($this->routing_paths as $path) - { - $loader = new YamlFileLoader(new FileLocator($base_path . $path)); - $routes->addCollection($loader->load('routing.yml')); - } - - return $routes; - } -} diff --git a/phpBB/includes/controller/resolver.php b/phpBB/includes/controller/resolver.php deleted file mode 100644 index 95dfc8da8e..0000000000 --- a/phpBB/includes/controller/resolver.php +++ /dev/null @@ -1,154 +0,0 @@ -user = $user; - $this->container = $container; - $this->style = $style; - } - - /** - * Load a controller callable - * - * @param Symfony\Component\HttpFoundation\Request $request Symfony Request object - * @return bool|Callable Callable or false - * @throws phpbb_controller_exception - */ - public function getController(Request $request) - { - $controller = $request->attributes->get('_controller'); - - if (!$controller) - { - throw new phpbb_controller_exception($this->user->lang['CONTROLLER_NOT_SPECIFIED']); - } - - // Require a method name along with the service name - if (stripos($controller, ':') === false) - { - throw new phpbb_controller_exception($this->user->lang['CONTROLLER_METHOD_NOT_SPECIFIED']); - } - - list($service, $method) = explode(':', $controller); - - if (!$this->container->has($service)) - { - throw new phpbb_controller_exception($this->user->lang('CONTROLLER_SERVICE_UNDEFINED', $service)); - } - - $controller_object = $this->container->get($service); - - /* - * If this is an extension controller, we'll try to automatically set - * the style paths for the extension (the ext author can change them - * if necessary). - */ - $controller_dir = explode('_', get_class($controller_object)); - - // 0 phpbb, 1 ext, 2 vendor, 3 extension name, ... - if (!is_null($this->style) && isset($controller_dir[3]) && $controller_dir[1] === 'ext') - { - $controller_style_dir = 'ext/' . $controller_dir[2] . '/' . $controller_dir[3] . '/styles'; - - if (is_dir($controller_style_dir)) - { - $this->style->set_style(array($controller_style_dir, 'styles')); - } - } - - return array($controller_object, $method); - } - - /** - * Dependencies should be specified in the service definition and can be - * then accessed in __construct(). Arguments are sent through the URL path - * and should match the parameters of the method you are using as your - * controller. - * - * @param Symfony\Component\HttpFoundation\Request $request Symfony Request object - * @param mixed $controller A callable (controller class, method) - * @return bool False - * @throws phpbb_controller_exception - */ - public function getArguments(Request $request, $controller) - { - // At this point, $controller contains the object and method name - list($object, $method) = $controller; - $mirror = new ReflectionMethod($object, $method); - - $arguments = array(); - $parameters = $mirror->getParameters(); - $attributes = $request->attributes->all(); - foreach ($parameters as $param) - { - if (array_key_exists($param->name, $attributes)) - { - $arguments[] = $attributes[$param->name]; - } - else if ($param->getClass() && $param->getClass()->isInstance($request)) - { - $arguments[] = $request; - } - else if ($param->isDefaultValueAvailable()) - { - $arguments[] = $param->getDefaultValue(); - } - else - { - throw new phpbb_controller_exception($this->user->lang('CONTROLLER_ARGUMENT_VALUE_MISSING', $param->getPosition() + 1, get_class($object) . ':' . $method, $param->name)); - } - } - - return $arguments; - } -} diff --git a/phpBB/includes/cron/manager.php b/phpBB/includes/cron/manager.php deleted file mode 100644 index 84c9650830..0000000000 --- a/phpBB/includes/cron/manager.php +++ /dev/null @@ -1,138 +0,0 @@ -phpbb_root_path = $phpbb_root_path; - $this->php_ext = $php_ext; - - $this->load_tasks($tasks); - } - - /** - * Loads tasks given by name, wraps them - * and puts them into $this->tasks. - * - * @param array|Traversable $tasks Array of instances of phpbb_cron_task - * - * @return null - */ - public function load_tasks($tasks) - { - foreach ($tasks as $task) - { - $this->tasks[] = $this->wrap_task($task); - } - } - - /** - * Finds a task that is ready to run. - * - * If several tasks are ready, any one of them could be returned. - * - * If no tasks are ready, null is returned. - * - * @return phpbb_cron_task_wrapper|null - */ - public function find_one_ready_task() - { - foreach ($this->tasks as $task) - { - if ($task->is_ready()) - { - return $task; - } - } - return null; - } - - /** - * Finds all tasks that are ready to run. - * - * @return array List of tasks which are ready to run (wrapped in phpbb_cron_task_wrapper). - */ - public function find_all_ready_tasks() - { - $tasks = array(); - foreach ($this->tasks as $task) - { - if ($task->is_ready()) - { - $tasks[] = $task; - } - } - return $tasks; - } - - /** - * Finds a task by name. - * - * If there is no task with the specified name, null is returned. - * - * Web runner uses this method to resolve names to tasks. - * - * @param string $name Name of the task to look up. - * @return phpbb_cron_task A task corresponding to the given name, or null. - */ - public function find_task($name) - { - foreach ($this->tasks as $task) - { - if ($task->get_name() == $name) - { - return $task; - } - } - return null; - } - - /** - * Wraps a task inside an instance of phpbb_cron_task_wrapper. - * - * @param phpbb_cron_task $task The task. - * @return phpbb_cron_task_wrapper The wrapped task. - */ - public function wrap_task(phpbb_cron_task $task) - { - return new phpbb_cron_task_wrapper($task, $this->phpbb_root_path, $this->php_ext); - } -} diff --git a/phpBB/includes/cron/task/base.php b/phpBB/includes/cron/task/base.php deleted file mode 100644 index 94a2f267b4..0000000000 --- a/phpBB/includes/cron/task/base.php +++ /dev/null @@ -1,76 +0,0 @@ -name; - } - - /** - * Sets the name of the task. - * - * @param string $name The task name - */ - public function set_name($name) - { - $this->name = $name; - } - - /** - * Returns whether this cron task can run, given current board configuration. - * - * For example, a cron task that prunes forums can only run when - * forum pruning is enabled. - * - * @return bool - */ - public function is_runnable() - { - return true; - } - - /** - * Returns whether this cron task should run now, because enough time - * has passed since it was last run. - * - * @return bool - */ - public function should_run() - { - return true; - } -} diff --git a/phpBB/includes/cron/task/core/prune_all_forums.php b/phpBB/includes/cron/task/core/prune_all_forums.php deleted file mode 100644 index 2c5d38cec0..0000000000 --- a/phpBB/includes/cron/task/core/prune_all_forums.php +++ /dev/null @@ -1,93 +0,0 @@ -phpbb_root_path = $phpbb_root_path; - $this->php_ext = $php_ext; - $this->config = $config; - $this->db = $db; - } - - /** - * Runs this cron task. - * - * @return null - */ - public function run() - { - if (!function_exists('auto_prune')) - { - include($this->phpbb_root_path . 'includes/functions_admin.' . $this->php_ext); - } - - $sql = 'SELECT forum_id, prune_next, enable_prune, prune_days, prune_viewed, forum_flags, prune_freq - FROM ' . FORUMS_TABLE . " - WHERE enable_prune = 1 - AND prune_next < " . time(); - $result = $this->db->sql_query($sql); - while ($row = $this->db->sql_fetchrow($result)) - { - if ($row['prune_days']) - { - auto_prune($row['forum_id'], 'posted', $row['forum_flags'], $row['prune_days'], $row['prune_freq']); - } - - if ($row['prune_viewed']) - { - auto_prune($row['forum_id'], 'viewed', $row['forum_flags'], $row['prune_viewed'], $row['prune_freq']); - } - } - $this->db->sql_freeresult($result); - } - - /** - * Returns whether this cron task can run, given current board configuration. - * - * This cron task will only run when system cron is utilised. - * - * @return bool - */ - public function is_runnable() - { - return (bool) $this->config['use_system_cron']; - } -} diff --git a/phpBB/includes/cron/task/core/prune_forum.php b/phpBB/includes/cron/task/core/prune_forum.php deleted file mode 100644 index e3c497f072..0000000000 --- a/phpBB/includes/cron/task/core/prune_forum.php +++ /dev/null @@ -1,163 +0,0 @@ -phpbb_root_path = $phpbb_root_path; - $this->php_ext = $php_ext; - $this->config = $config; - $this->db = $db; - } - - /** - * Manually set forum data. - * - * @param array $forum_data Information about a forum to be pruned. - */ - public function set_forum_data($forum_data) - { - $this->forum_data = $forum_data; - } - - /** - * Runs this cron task. - * - * @return null - */ - public function run() - { - if (!function_exists('auto_prune')) - { - include($this->phpbb_root_path . 'includes/functions_admin.' . $this->php_ext); - } - - if ($this->forum_data['prune_days']) - { - auto_prune($this->forum_data['forum_id'], 'posted', $this->forum_data['forum_flags'], $this->forum_data['prune_days'], $this->forum_data['prune_freq']); - } - - if ($this->forum_data['prune_viewed']) - { - auto_prune($this->forum_data['forum_id'], 'viewed', $this->forum_data['forum_flags'], $this->forum_data['prune_viewed'], $this->forum_data['prune_freq']); - } - } - - /** - * Returns whether this cron task can run, given current board configuration. - * - * This cron task will not run when system cron is utilised, as in - * such cases prune_all_forums task would run instead. - * - * Additionally, this task must be given the forum data, either via - * the constructor or parse_parameters method. - * - * @return bool - */ - public function is_runnable() - { - return !$this->config['use_system_cron'] && $this->forum_data; - } - - /** - * Returns whether this cron task should run now, because enough time - * has passed since it was last run. - * - * Forum pruning interval is specified in the forum data. - * - * @return bool - */ - public function should_run() - { - return $this->forum_data['enable_prune'] && $this->forum_data['prune_next'] < time(); - } - - /** - * Returns parameters of this cron task as an array. - * The array has one key, f, whose value is id of the forum to be pruned. - * - * @return array - */ - public function get_parameters() - { - return array('f' => $this->forum_data['forum_id']); - } - - /** - * Parses parameters found in $request, which is an instance of - * phpbb_request_interface. - * - * It is expected to have a key f whose value is id of the forum to be pruned. - * - * @param phpbb_request_interface $request Request object. - * - * @return null - */ - public function parse_parameters(phpbb_request_interface $request) - { - $this->forum_data = null; - if ($request->is_set('f')) - { - $forum_id = $request->variable('f', 0); - - $sql = 'SELECT forum_id, prune_next, enable_prune, prune_days, prune_viewed, forum_flags, prune_freq - FROM ' . FORUMS_TABLE . " - WHERE forum_id = $forum_id"; - $result = $this->db->sql_query($sql); - $row = $this->db->sql_fetchrow($result); - $this->db->sql_freeresult($result); - - if ($row) - { - $this->forum_data = $row; - } - } - } -} diff --git a/phpBB/includes/cron/task/core/queue.php b/phpBB/includes/cron/task/core/queue.php deleted file mode 100644 index 732f9c6bea..0000000000 --- a/phpBB/includes/cron/task/core/queue.php +++ /dev/null @@ -1,82 +0,0 @@ -phpbb_root_path = $phpbb_root_path; - $this->php_ext = $php_ext; - $this->config = $config; - } - - /** - * Runs this cron task. - * - * @return null - */ - public function run() - { - if (!class_exists('queue')) - { - include($this->phpbb_root_path . 'includes/functions_messenger.' . $this->php_ext); - } - $queue = new queue(); - $queue->process(); - } - - /** - * Returns whether this cron task can run, given current board configuration. - * - * Queue task is only run if the email queue (file) exists. - * - * @return bool - */ - public function is_runnable() - { - return file_exists($this->phpbb_root_path . 'cache/queue.' . $this->php_ext); - } - - /** - * Returns whether this cron task should run now, because enough time - * has passed since it was last run. - * - * The interval between queue runs is specified in board configuration. - * - * @return bool - */ - public function should_run() - { - return $this->config['last_queue_run'] < time() - $this->config['queue_interval_config']; - } -} diff --git a/phpBB/includes/cron/task/core/tidy_cache.php b/phpBB/includes/cron/task/core/tidy_cache.php deleted file mode 100644 index 16a45dae7c..0000000000 --- a/phpBB/includes/cron/task/core/tidy_cache.php +++ /dev/null @@ -1,76 +0,0 @@ -config = $config; - $this->cache = $cache; - } - - /** - * Runs this cron task. - * - * @return null - */ - public function run() - { - $this->cache->tidy(); - } - - /** - * Returns whether this cron task can run, given current board configuration. - * - * Tidy cache cron task runs if the cache implementation in use - * supports tidying. - * - * @return bool - */ - public function is_runnable() - { - return true; - } - - /** - * Returns whether this cron task should run now, because enough time - * has passed since it was last run. - * - * The interval between cache tidying is specified in board - * configuration. - * - * @return bool - */ - public function should_run() - { - return $this->config['cache_last_gc'] < time() - $this->config['cache_gc']; - } -} diff --git a/phpBB/includes/cron/task/core/tidy_database.php b/phpBB/includes/cron/task/core/tidy_database.php deleted file mode 100644 index b882e7b500..0000000000 --- a/phpBB/includes/cron/task/core/tidy_database.php +++ /dev/null @@ -1,70 +0,0 @@ -phpbb_root_path = $phpbb_root_path; - $this->php_ext = $php_ext; - $this->config = $config; - } - - /** - * Runs this cron task. - * - * @return null - */ - public function run() - { - if (!function_exists('tidy_database')) - { - include($this->phpbb_root_path . 'includes/functions_admin.' . $this->php_ext); - } - tidy_database(); - } - - /** - * Returns whether this cron task should run now, because enough time - * has passed since it was last run. - * - * The interval between database tidying is specified in board - * configuration. - * - * @return bool - */ - public function should_run() - { - return $this->config['database_last_gc'] < time() - $this->config['database_gc']; - } -} diff --git a/phpBB/includes/cron/task/core/tidy_search.php b/phpBB/includes/cron/task/core/tidy_search.php deleted file mode 100644 index 3ec25aa021..0000000000 --- a/phpBB/includes/cron/task/core/tidy_search.php +++ /dev/null @@ -1,109 +0,0 @@ -phpbb_root_path = $phpbb_root_path; - $this->php_ext = $php_ext; - $this->auth = $auth; - $this->config = $config; - $this->db = $db; - $this->user = $user; - } - - /** - * Runs this cron task. - * - * @return null - */ - public function run() - { - // Select the search method - $search_type = basename($this->config['search_type']); - - if (!class_exists($search_type)) - { - include($this->phpbb_root_path . "includes/search/$search_type." . $this->php_ext); - } - - // We do some additional checks in the module to ensure it can actually be utilised - $error = false; - $search = new $search_type($error, $this->phpbb_root_path, $this->php_ext, $this->auth, $this->config, $this->db, $this->user); - - if (!$error) - { - $search->tidy(); - } - } - - /** - * Returns whether this cron task can run, given current board configuration. - * - * Search cron task is runnable in all normal use. It may not be - * runnable if the search backend implementation selected in board - * configuration does not exist. - * - * @return bool - */ - public function is_runnable() - { - // Select the search method - $search_type = basename($this->config['search_type']); - - return file_exists($this->phpbb_root_path . 'includes/search/' . $search_type . '.' . $this->php_ext); - } - - /** - * Returns whether this cron task should run now, because enough time - * has passed since it was last run. - * - * The interval between search tidying is specified in board - * configuration. - * - * @return bool - */ - public function should_run() - { - return $this->config['search_last_gc'] < time() - $this->config['search_gc']; - } -} diff --git a/phpBB/includes/cron/task/core/tidy_sessions.php b/phpBB/includes/cron/task/core/tidy_sessions.php deleted file mode 100644 index 95f55235c9..0000000000 --- a/phpBB/includes/cron/task/core/tidy_sessions.php +++ /dev/null @@ -1,63 +0,0 @@ -config = $config; - $this->user = $user; - } - - /** - * Runs this cron task. - * - * @return null - */ - public function run() - { - $this->user->session_gc(); - } - - /** - * Returns whether this cron task should run now, because enough time - * has passed since it was last run. - * - * The interval between session tidying is specified in board - * configuration. - * - * @return bool - */ - public function should_run() - { - return $this->config['session_last_gc'] < time() - $this->config['session_gc']; - } -} diff --git a/phpBB/includes/cron/task/core/tidy_warnings.php b/phpBB/includes/cron/task/core/tidy_warnings.php deleted file mode 100644 index 2a7798e56e..0000000000 --- a/phpBB/includes/cron/task/core/tidy_warnings.php +++ /dev/null @@ -1,84 +0,0 @@ -phpbb_root_path = $phpbb_root_path; - $this->php_ext = $php_ext; - $this->config = $config; - } - - /** - * Runs this cron task. - * - * @return null - */ - public function run() - { - if (!function_exists('tidy_warnings')) - { - include($this->phpbb_root_path . 'includes/functions_admin.' . $this->php_ext); - } - tidy_warnings(); - } - - /** - * Returns whether this cron task can run, given current board configuration. - * - * If warnings are set to never expire, this cron task will not run. - * - * @return bool - */ - public function is_runnable() - { - return (bool) $this->config['warnings_expire_days']; - } - - /** - * Returns whether this cron task should run now, because enough time - * has passed since it was last run. - * - * The interval between warnings tidying is specified in board - * configuration. - * - * @return bool - */ - public function should_run() - { - return $this->config['warnings_last_gc'] < time() - $this->config['warnings_gc']; - } -} diff --git a/phpBB/includes/cron/task/parametrized.php b/phpBB/includes/cron/task/parametrized.php deleted file mode 100644 index 5f0e46eafc..0000000000 --- a/phpBB/includes/cron/task/parametrized.php +++ /dev/null @@ -1,52 +0,0 @@ -task = $task; - $this->phpbb_root_path = $phpbb_root_path; - $this->php_ext = $php_ext; - } - - /** - * Returns whether the wrapped task is parametrised. - * - * Parametrized tasks accept parameters during initialization and must - * normally be scheduled with parameters. - * - * @return bool Whether or not this task is parametrized. - */ - public function is_parametrized() - { - return $this->task instanceof phpbb_cron_task_parametrized; - } - - /** - * Returns whether the wrapped task is ready to run. - * - * A task is ready to run when it is runnable according to current configuration - * and enough time has passed since it was last run. - * - * @return bool Whether the wrapped task is ready to run. - */ - public function is_ready() - { - return $this->task->is_runnable() && $this->task->should_run(); - } - - /** - * Returns a url through which this task may be invoked via web. - * - * When system cron is not in use, running a cron task is accomplished - * by outputting an image with the url returned by this function as - * source. - * - * @return string URL through which this task may be invoked. - */ - public function get_url() - { - $name = $this->get_name(); - if ($this->is_parametrized()) - { - $params = $this->task->get_parameters(); - $extra = ''; - foreach ($params as $key => $value) - { - $extra .= '&' . $key . '=' . urlencode($value); - } - } - else - { - $extra = ''; - } - $url = append_sid($this->phpbb_root_path . 'cron.' . $this->php_ext, 'cron_type=' . $name . $extra); - return $url; - } - - /** - * Forwards all other method calls to the wrapped task implementation. - * - * @return mixed - */ - public function __call($name, $args) - { - return call_user_func_array(array($this->task, $name), $args); - } -} diff --git a/phpBB/includes/datetime.php b/phpBB/includes/datetime.php deleted file mode 100644 index 3c6d4971b9..0000000000 --- a/phpBB/includes/datetime.php +++ /dev/null @@ -1,158 +0,0 @@ -user = $user; - $timezone = $timezone ?: $this->user->timezone; - - parent::__construct($time, $timezone); - } - - /** - * Formats the current date time into the specified format - * - * @param string $format Optional format to use for output, defaults to users chosen format - * @param boolean $force_absolute Force output of a non relative date - * @return string Formatted date time - */ - public function format($format = '', $force_absolute = false) - { - $format = $format ? $format : $this->user->date_format; - $format = self::format_cache($format, $this->user); - $relative = ($format['is_short'] && !$force_absolute); - $now = new self($this->user, 'now', $this->user->timezone); - - $timestamp = $this->getTimestamp(); - $now_ts = $now->getTimeStamp(); - - $delta = $now_ts - $timestamp; - - if ($relative) - { - /* - * Check the delta is less than or equal to 1 hour - * and the delta not more than a minute in the past - * and the delta is either greater than -5 seconds or timestamp - * and current time are of the same minute (they must be in the same hour already) - * finally check that relative dates are supported by the language pack - */ - if ($delta <= 3600 && $delta > -60 && - ($delta >= -5 || (($now_ts / 60) % 60) == (($timestamp / 60) % 60)) - && isset($this->user->lang['datetime']['AGO'])) - { - return $this->user->lang(array('datetime', 'AGO'), max(0, (int) floor($delta / 60))); - } - else - { - $midnight = clone $now; - $midnight->setTime(0, 0, 0); - - $midnight = $midnight->getTimestamp(); - - $day = false; - - if ($timestamp > $midnight + 86400) - { - $day = 'TOMORROW'; - } - else if ($timestamp > $midnight) - { - $day = 'TODAY'; - } - else if ($timestamp > $midnight - 86400) - { - $day = 'YESTERDAY'; - } - - if ($day !== false) - { - // Format using the short formatting and finally swap out the relative token placeholder with the correct value - return str_replace(self::RELATIVE_WRAPPER . self::RELATIVE_WRAPPER, $this->user->lang['datetime'][$day], strtr(parent::format($format['format_short']), $format['lang'])); - } - } - } - - return strtr(parent::format($format['format_long']), $format['lang']); - } - - /** - * Magic method to convert DateTime object to string - * - * @return Formatted date time, according to the users default settings. - */ - public function __toString() - { - return $this->format(); - } - - /** - * Pre-processes the specified date format - * - * @param string $format Output format - * @param user $user User object to use for localisation - * @return array Processed date format - */ - static protected function format_cache($format, $user) - { - $lang = $user->lang_name; - - if (!isset(self::$format_cache[$lang])) - { - self::$format_cache[$lang] = array(); - } - - if (!isset(self::$format_cache[$lang][$format])) - { - // Is the user requesting a friendly date format (i.e. 'Today 12:42')? - self::$format_cache[$lang][$format] = array( - 'is_short' => strpos($format, self::RELATIVE_WRAPPER) !== false, - 'format_short' => substr($format, 0, strpos($format, self::RELATIVE_WRAPPER)) . self::RELATIVE_WRAPPER . self::RELATIVE_WRAPPER . substr(strrchr($format, self::RELATIVE_WRAPPER), 1), - 'format_long' => str_replace(self::RELATIVE_WRAPPER, '', $format), - 'lang' => array_filter($user->lang['datetime'], 'is_string'), - ); - - // Short representation of month in format? Some languages use different terms for the long and short format of May - if ((strpos($format, '\M') === false && strpos($format, 'M') !== false) || (strpos($format, '\r') === false && strpos($format, 'r') !== false)) - { - self::$format_cache[$lang][$format]['lang']['May'] = $user->lang['datetime']['May_short']; - } - } - - return self::$format_cache[$lang][$format]; - } -} diff --git a/phpBB/includes/db/driver/driver.php b/phpBB/includes/db/driver/driver.php deleted file mode 100644 index 08c966c07a..0000000000 --- a/phpBB/includes/db/driver/driver.php +++ /dev/null @@ -1,1044 +0,0 @@ -num_queries = array( - '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), 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) . '%'; - $this->one_char = chr(0) . '_'; - } - - /** - * return on error or display error message - */ - function sql_return_on_error($fail = false) - { - $this->sql_error_triggered = false; - $this->sql_error_sql = ''; - - $this->return_on_error = $fail; - } - - /** - * Return number of sql queries and cached sql queries used - */ - function sql_num_queries($cached = false) - { - return ($cached) ? $this->num_queries['cached'] : $this->num_queries['normal']; - } - - /** - * Add to query count - */ - function sql_add_num_queries($cached = false) - { - $this->num_queries['cached'] += ($cached !== false) ? 1 : 0; - $this->num_queries['normal'] += ($cached !== false) ? 0 : 1; - $this->num_queries['total'] += 1; - } - - /** - * DBAL garbage collection, close sql connection - */ - function sql_close() - { - if (!$this->db_connect_id) - { - return false; - } - - if ($this->transaction) - { - do - { - $this->sql_transaction('commit'); - } - while ($this->transaction); - } - - foreach ($this->open_queries as $query_id) - { - $this->sql_freeresult($query_id); - } - - // Connection closed correctly. Set db_connect_id to false to prevent errors - if ($result = $this->_sql_close()) - { - $this->db_connect_id = false; - } - - return $result; - } - - /** - * Build LIMIT query - * Doing some validation here. - */ - function sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) - { - if (empty($query)) - { - return false; - } - - // Never use a negative total or offset - $total = ($total < 0) ? 0 : $total; - $offset = ($offset < 0) ? 0 : $offset; - - return $this->_sql_query_limit($query, $total, $offset, $cache_ttl); - } - - /** - * Fetch all rows - */ - function sql_fetchrowset($query_id = false) - { - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if ($query_id !== false) - { - $result = array(); - while ($row = $this->sql_fetchrow($query_id)) - { - $result[] = $row; - } - - return $result; - } - - return false; - } - - /** - * Seek to given row number - * rownum is zero-based - */ - function sql_rowseek($rownum, &$query_id) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if ($cache && $cache->sql_exists($query_id)) - { - return $cache->sql_rowseek($rownum, $query_id); - } - - if ($query_id === false) - { - return false; - } - - $this->sql_freeresult($query_id); - $query_id = $this->sql_query($this->last_query_text); - - if ($query_id === false) - { - return false; - } - - // We do not fetch the row for rownum == 0 because then the next resultset would be the second row - for ($i = 0; $i < $rownum; $i++) - { - if (!$this->sql_fetchrow($query_id)) - { - return false; - } - } - - return true; - } - - /** - * Fetch field - * if rownum is false, the current row is used, else it is pointing to the row (zero-based) - */ - function sql_fetchfield($field, $rownum = false, $query_id = false) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if ($query_id !== false) - { - if ($rownum !== false) - { - $this->sql_rowseek($rownum, $query_id); - } - - if ($cache && !is_object($query_id) && $cache->sql_exists($query_id)) - { - return $cache->sql_fetchfield($query_id, $field); - } - - $row = $this->sql_fetchrow($query_id); - return (isset($row[$field])) ? $row[$field] : false; - } - - return false; - } - - /** - * Correctly adjust LIKE expression for special characters - * Some DBMS are handling them in a different way - * - * @param string $expression The expression to use. Every wildcard is escaped, except $this->any_char and $this->one_char - * @return string LIKE expression including the keyword! - */ - function sql_like_expression($expression) - { - $expression = utf8_str_replace(array('_', '%'), array("\_", "\%"), $expression); - $expression = utf8_str_replace(array(chr(0) . "\_", chr(0) . "\%"), array('_', '%'), $expression); - - return $this->_sql_like_expression('LIKE \'' . $this->sql_escape($expression) . '\''); - } - - /** - * Build a case expression - * - * Note: The two statements action_true and action_false must have the same data type (int, vchar, ...) in the database! - * - * @param string $condition The condition which must be true, to use action_true rather then action_else - * @param string $action_true SQL expression that is used, if the condition is true - * @param string $action_else SQL expression that is used, if the condition is false, optional - * @return string CASE expression including the condition and statements - */ - public function sql_case($condition, $action_true, $action_false = false) - { - $sql_case = 'CASE WHEN ' . $condition; - $sql_case .= ' THEN ' . $action_true; - $sql_case .= ($action_false !== false) ? ' ELSE ' . $action_false : ''; - $sql_case .= ' END'; - return $sql_case; - } - - /** - * Build a concatenated expression - * - * @param string $expr1 Base SQL expression where we append the second one - * @param string $expr2 SQL expression that is appended to the first expression - * @return string Concatenated string - */ - public function sql_concatenate($expr1, $expr2) - { - return $expr1 . ' || ' . $expr2; - } - - /** - * Returns whether results of a query need to be buffered to run a transaction while iterating over them. - * - * @return bool Whether buffering is required. - */ - function sql_buffer_nested_transactions() - { - return false; - } - - /** - * SQL Transaction - * @access private - */ - function sql_transaction($status = 'begin') - { - switch ($status) - { - case 'begin': - // If we are within a transaction we will not open another one, but enclose the current one to not loose data (prevening auto commit) - if ($this->transaction) - { - $this->transactions++; - return true; - } - - $result = $this->_sql_transaction('begin'); - - if (!$result) - { - $this->sql_error(); - } - - $this->transaction = true; - break; - - case 'commit': - // If there was a previously opened transaction we do not commit yet... but count back the number of inner transactions - if ($this->transaction && $this->transactions) - { - $this->transactions--; - return true; - } - - // Check if there is a transaction (no transaction can happen if there was an error, with a combined rollback and error returning enabled) - // This implies we have transaction always set for autocommit db's - if (!$this->transaction) - { - return false; - } - - $result = $this->_sql_transaction('commit'); - - if (!$result) - { - $this->sql_error(); - } - - $this->transaction = false; - $this->transactions = 0; - break; - - case 'rollback': - $result = $this->_sql_transaction('rollback'); - $this->transaction = false; - $this->transactions = 0; - break; - - default: - $result = $this->_sql_transaction($status); - break; - } - - return $result; - } - - /** - * Build sql statement from array for insert/update/select statements - * - * Idea for this from Ikonboard - * Possible query values: INSERT, INSERT_SELECT, UPDATE, SELECT - * - */ - function sql_build_array($query, $assoc_ary = false) - { - if (!is_array($assoc_ary)) - { - return false; - } - - $fields = $values = array(); - - if ($query == 'INSERT' || $query == 'INSERT_SELECT') - { - foreach ($assoc_ary as $key => $var) - { - $fields[] = $key; - - if (is_array($var) && is_string($var[0])) - { - // This is used for INSERT_SELECT(s) - $values[] = $var[0]; - } - else - { - $values[] = $this->_sql_validate_value($var); - } - } - - $query = ($query == 'INSERT') ? ' (' . implode(', ', $fields) . ') VALUES (' . implode(', ', $values) . ')' : ' (' . implode(', ', $fields) . ') SELECT ' . implode(', ', $values) . ' '; - } - else if ($query == 'MULTI_INSERT') - { - trigger_error('The MULTI_INSERT query value is no longer supported. Please use sql_multi_insert() instead.', E_USER_ERROR); - } - else if ($query == 'UPDATE' || $query == 'SELECT') - { - $values = array(); - foreach ($assoc_ary as $key => $var) - { - $values[] = "$key = " . $this->_sql_validate_value($var); - } - $query = implode(($query == 'UPDATE') ? ', ' : ' AND ', $values); - } - - return $query; - } - - /** - * Build IN or NOT IN sql comparison string, uses <> or = on single element - * arrays to improve comparison speed - * - * @access public - * @param string $field name of the sql column that shall be compared - * @param array $array array of values that are allowed (IN) or not allowed (NOT IN) - * @param bool $negate true for NOT IN (), false for IN () (default) - * @param bool $allow_empty_set If true, allow $array to be empty, this function will return 1=1 or 1=0 then. Default to false. - */ - function sql_in_set($field, $array, $negate = false, $allow_empty_set = false) - { - if (!sizeof($array)) - { - if (!$allow_empty_set) - { - // Print the backtrace to help identifying the location of the problematic code - $this->sql_error('No values specified for SQL IN comparison'); - } - else - { - // NOT IN () actually means everything so use a tautology - if ($negate) - { - return '1=1'; - } - // IN () actually means nothing so use a contradiction - else - { - return '1=0'; - } - } - } - - if (!is_array($array)) - { - $array = array($array); - } - - if (sizeof($array) == 1) - { - @reset($array); - $var = current($array); - - return $field . ($negate ? ' <> ' : ' = ') . $this->_sql_validate_value($var); - } - else - { - return $field . ($negate ? ' NOT IN ' : ' IN ') . '(' . implode(', ', array_map(array($this, '_sql_validate_value'), $array)) . ')'; - } - } - - /** - * Run binary AND operator on DB column. - * Results in sql statement: "{$column_name} & (1 << {$bit}) {$compare}" - * - * @param string $column_name The column name to use - * @param int $bit The value to use for the AND operator, will be converted to (1 << $bit). Is used by options, using the number schema... 0, 1, 2...29 - * @param string $compare Any custom SQL code after the check (for example "= 0") - */ - function sql_bit_and($column_name, $bit, $compare = '') - { - if (method_exists($this, '_sql_bit_and')) - { - return $this->_sql_bit_and($column_name, $bit, $compare); - } - - return $column_name . ' & ' . (1 << $bit) . (($compare) ? ' ' . $compare : ''); - } - - /** - * Run binary OR operator on DB column. - * Results in sql statement: "{$column_name} | (1 << {$bit}) {$compare}" - * - * @param string $column_name The column name to use - * @param int $bit The value to use for the OR operator, will be converted to (1 << $bit). Is used by options, using the number schema... 0, 1, 2...29 - * @param string $compare Any custom SQL code after the check (for example "= 0") - */ - function sql_bit_or($column_name, $bit, $compare = '') - { - if (method_exists($this, '_sql_bit_or')) - { - return $this->_sql_bit_or($column_name, $bit, $compare); - } - - return $column_name . ' | ' . (1 << $bit) . (($compare) ? ' ' . $compare : ''); - } - - /** - * Returns SQL string to cast a string expression to an int. - * - * @param string $expression An expression evaluating to string - * @return string Expression returning an int - */ - function cast_expr_to_bigint($expression) - { - return $expression; - } - - /** - * Returns SQL string to cast an integer expression to a string. - * - * @param string $expression An expression evaluating to int - * @return string Expression returning a string - */ - function cast_expr_to_string($expression) - { - return $expression; - } - - /** - * Run LOWER() on DB column of type text (i.e. neither varchar nor char). - * - * @param string $column_name The column name to use - * - * @return string A SQL statement like "LOWER($column_name)" - */ - function sql_lower_text($column_name) - { - return "LOWER($column_name)"; - } - - /** - * Run more than one insert statement. - * - * @param string $table table name to run the statements on - * @param array $sql_ary multi-dimensional array holding the statement data. - * - * @return bool false if no statements were executed. - * @access public - */ - function sql_multi_insert($table, $sql_ary) - { - if (!sizeof($sql_ary)) - { - return false; - } - - if ($this->multi_insert) - { - $ary = array(); - foreach ($sql_ary as $id => $_sql_ary) - { - // If by accident the sql array is only one-dimensional we build a normal insert statement - if (!is_array($_sql_ary)) - { - return $this->sql_query('INSERT INTO ' . $table . ' ' . $this->sql_build_array('INSERT', $sql_ary)); - } - - $values = array(); - foreach ($_sql_ary as $key => $var) - { - $values[] = $this->_sql_validate_value($var); - } - $ary[] = '(' . implode(', ', $values) . ')'; - } - - return $this->sql_query('INSERT INTO ' . $table . ' ' . ' (' . implode(', ', array_keys($sql_ary[0])) . ') VALUES ' . implode(', ', $ary)); - } - else - { - foreach ($sql_ary as $ary) - { - if (!is_array($ary)) - { - return false; - } - - $result = $this->sql_query('INSERT INTO ' . $table . ' ' . $this->sql_build_array('INSERT', $ary)); - - if (!$result) - { - return false; - } - } - } - - return true; - } - - /** - * Function for validating values - * @access private - */ - function _sql_validate_value($var) - { - if (is_null($var)) - { - return 'NULL'; - } - else if (is_string($var)) - { - return "'" . $this->sql_escape($var) . "'"; - } - else - { - return (is_bool($var)) ? intval($var) : $var; - } - } - - /** - * Build sql statement from array for select and select distinct statements - * - * Possible query values: SELECT, SELECT_DISTINCT - */ - function sql_build_query($query, $array) - { - $sql = ''; - switch ($query) - { - case 'SELECT': - case 'SELECT_DISTINCT'; - - $sql = str_replace('_', ' ', $query) . ' ' . $array['SELECT'] . ' FROM '; - - // Build table array. We also build an alias array for later checks. - $table_array = $aliases = array(); - $used_multi_alias = false; - - foreach ($array['FROM'] as $table_name => $alias) - { - if (is_array($alias)) - { - $used_multi_alias = true; - - foreach ($alias as $multi_alias) - { - $table_array[] = $table_name . ' ' . $multi_alias; - $aliases[] = $multi_alias; - } - } - else - { - $table_array[] = $table_name . ' ' . $alias; - $aliases[] = $alias; - } - } - - // We run the following code to determine if we need to re-order the table array. ;) - // The reason for this is that for multi-aliased tables (two equal tables) in the FROM statement the last table need to match the first comparison. - // DBMS who rely on this: Oracle, PostgreSQL and MSSQL. For all other DBMS it makes absolutely no difference in which order the table is. - if (!empty($array['LEFT_JOIN']) && sizeof($array['FROM']) > 1 && $used_multi_alias !== false) - { - // Take first LEFT JOIN - $join = current($array['LEFT_JOIN']); - - // Determine the table used there (even if there are more than one used, we only want to have one - preg_match('/(' . implode('|', $aliases) . ')\.[^\s]+/U', str_replace(array('(', ')', 'AND', 'OR', ' '), '', $join['ON']), $matches); - - // If there is a first join match, we need to make sure the table order is correct - if (!empty($matches[1])) - { - $first_join_match = trim($matches[1]); - $table_array = $last = array(); - - foreach ($array['FROM'] as $table_name => $alias) - { - if (is_array($alias)) - { - foreach ($alias as $multi_alias) - { - ($multi_alias === $first_join_match) ? $last[] = $table_name . ' ' . $multi_alias : $table_array[] = $table_name . ' ' . $multi_alias; - } - } - else - { - ($alias === $first_join_match) ? $last[] = $table_name . ' ' . $alias : $table_array[] = $table_name . ' ' . $alias; - } - } - - $table_array = array_merge($table_array, $last); - } - } - - $sql .= $this->_sql_custom_build('FROM', implode(' CROSS JOIN ', $table_array)); - - if (!empty($array['LEFT_JOIN'])) - { - foreach ($array['LEFT_JOIN'] as $join) - { - $sql .= ' LEFT JOIN ' . key($join['FROM']) . ' ' . current($join['FROM']) . ' ON (' . $join['ON'] . ')'; - } - } - - if (!empty($array['WHERE'])) - { - $sql .= ' WHERE ' . $this->_sql_custom_build('WHERE', $array['WHERE']); - } - - if (!empty($array['GROUP_BY'])) - { - $sql .= ' GROUP BY ' . $array['GROUP_BY']; - } - - if (!empty($array['ORDER_BY'])) - { - $sql .= ' ORDER BY ' . $array['ORDER_BY']; - } - - break; - } - - return $sql; - } - - /** - * display sql error page - */ - function sql_error($sql = '') - { - global $auth, $user, $config; - - // Set var to retrieve errored status - $this->sql_error_triggered = true; - $this->sql_error_sql = $sql; - - $this->sql_error_returned = $this->_sql_error(); - - if (!$this->return_on_error) - { - $message = 'SQL ERROR [ ' . $this->sql_layer . ' ]

' . $this->sql_error_returned['message'] . ' [' . $this->sql_error_returned['code'] . ']'; - - // Show complete SQL error and path to administrators only - // Additionally show complete error on installation or if extended debug mode is enabled - // The DEBUG constant is for development only! - if ((isset($auth) && $auth->acl_get('a_')) || defined('IN_INSTALL') || defined('DEBUG')) - { - $message .= ($sql) ? '

SQL

' . htmlspecialchars($sql) : ''; - } - else - { - // If error occurs in initiating the session we need to use a pre-defined language string - // This could happen if the connection could not be established for example (then we are not able to grab the default language) - if (!isset($user->lang['SQL_ERROR_OCCURRED'])) - { - $message .= '

An sql error occurred while fetching this page. Please contact an administrator if this problem persists.'; - } - else - { - if (!empty($config['board_contact'])) - { - $message .= '

' . sprintf($user->lang['SQL_ERROR_OCCURRED'], '', ''); - } - else - { - $message .= '

' . sprintf($user->lang['SQL_ERROR_OCCURRED'], '', ''); - } - } - } - - if ($this->transaction) - { - $this->sql_transaction('rollback'); - } - - if (strlen($message) > 1024) - { - // We need to define $msg_long_text here to circumvent text stripping. - global $msg_long_text; - $msg_long_text = $message; - - trigger_error(false, E_USER_ERROR); - } - - trigger_error($message, E_USER_ERROR); - } - - if ($this->transaction) - { - $this->sql_transaction('rollback'); - } - - return $this->sql_error_returned; - } - - /** - * Explain queries - */ - function sql_report($mode, $query = '') - { - global $cache, $starttime, $phpbb_root_path, $phpbb_admin_path, $user; - global $request; - - if (is_object($request) && !$request->variable('explain', false)) - { - return false; - } - - if (!$query && $this->query_hold != '') - { - $query = $this->query_hold; - } - - switch ($mode) - { - case 'display': - if (!empty($cache)) - { - $cache->unload(); - } - $this->sql_close(); - - $mtime = explode(' ', microtime()); - $totaltime = $mtime[0] + $mtime[1] - $starttime; - - echo ' - - - - SQL Report - - - -
- -
-
-
- -
-

SQL Report

-
-

Page generated in ' . round($totaltime, 4) . " seconds with {$this->num_queries['normal']} queries" . (($this->num_queries['cached']) ? " + {$this->num_queries['cached']} " . (($this->num_queries['cached'] == 1) ? 'query' : 'queries') . ' returning data from cache' : '') . '

- -

Time spent on ' . $this->sql_layer . ' queries: ' . round($this->sql_time, 5) . 's | Time spent on PHP: ' . round($totaltime - $this->sql_time, 5) . 's

- -

- ' . $this->sql_report . ' -
- -
-
-
- -
- - '; - - exit_handler(); - - break; - - case 'stop': - $endtime = explode(' ', microtime()); - $endtime = $endtime[0] + $endtime[1]; - - $this->sql_report .= ' - - - - - - - - - - - - -
Query #' . $this->num_queries['total'] . '
- - ' . $this->html_hold . ' - -

- '; - - if ($this->query_result) - { - if (preg_match('/^(UPDATE|DELETE|REPLACE)/', $query)) - { - $this->sql_report .= 'Affected rows: ' . $this->sql_affectedrows($this->query_result) . ' | '; - } - $this->sql_report .= 'Before: ' . sprintf('%.5f', $this->curtime - $starttime) . 's | After: ' . sprintf('%.5f', $endtime - $starttime) . 's | Elapsed: ' . sprintf('%.5f', $endtime - $this->curtime) . 's'; - } - else - { - $error = $this->sql_error(); - $this->sql_report .= 'FAILED - ' . $this->sql_layer . ' Error ' . $error['code'] . ': ' . htmlspecialchars($error['message']); - } - - $this->sql_report .= '



'; - - $this->sql_time += $endtime - $this->curtime; - break; - - case 'start': - $this->query_hold = $query; - $this->html_hold = ''; - - $this->_sql_report($mode, $query); - - $this->curtime = explode(' ', microtime()); - $this->curtime = $this->curtime[0] + $this->curtime[1]; - - break; - - case 'add_select_row': - - $html_table = func_get_arg(2); - $row = func_get_arg(3); - - if (!$html_table && sizeof($row)) - { - $html_table = true; - $this->html_hold .= ''; - - foreach (array_keys($row) as $val) - { - $this->html_hold .= ''; - } - $this->html_hold .= ''; - } - $this->html_hold .= ''; - - $class = 'row1'; - foreach (array_values($row) as $val) - { - $class = ($class == 'row1') ? 'row2' : 'row1'; - $this->html_hold .= ''; - } - $this->html_hold .= ''; - - return $html_table; - - break; - - case 'fromcache': - - $this->_sql_report($mode, $query); - - break; - - case 'record_fromcache': - - $endtime = func_get_arg(2); - $splittime = func_get_arg(3); - - $time_cache = $endtime - $this->curtime; - $time_db = $splittime - $endtime; - $color = ($time_db > $time_cache) ? 'green' : 'red'; - - $this->sql_report .= '
' . (($val) ? ucwords(str_replace('_', ' ', $val)) : ' ') . '
' . (($val) ? $val : ' ') . '
'; - $this->sql_report .= '
Query results obtained from the cache
'; - $this->sql_report .= '

'; - $this->sql_report .= 'Before: ' . sprintf('%.5f', $this->curtime - $starttime) . 's | After: ' . sprintf('%.5f', $endtime - $starttime) . 's | Elapsed [cache]: ' . sprintf('%.5f', ($time_cache)) . 's | Elapsed [db]: ' . sprintf('%.5f', $time_db) . 's



'; - - // Pad the start time to not interfere with page timing - $starttime += $time_db; - - break; - - default: - - $this->_sql_report($mode, $query); - - break; - } - - return true; - } - - /** - * Gets the estimated number of rows in a specified table. - * - * @param string $table_name Table name - * - * @return string Number of rows in $table_name. - * Prefixed with ~ if estimated (otherwise exact). - * - * @access public - */ - function get_estimated_row_count($table_name) - { - return $this->get_row_count($table_name); - } - - /** - * Gets the exact number of rows in a specified table. - * - * @param string $table_name Table name - * - * @return string Exact number of rows in $table_name. - * - * @access public - */ - function get_row_count($table_name) - { - $sql = 'SELECT COUNT(*) AS rows_total - FROM ' . $this->sql_escape($table_name); - $result = $this->sql_query($sql); - $rows_total = $this->sql_fetchfield('rows_total'); - $this->sql_freeresult($result); - - return $rows_total; - } -} diff --git a/phpBB/includes/db/driver/firebird.php b/phpBB/includes/db/driver/firebird.php deleted file mode 100644 index 787c28b812..0000000000 --- a/phpBB/includes/db/driver/firebird.php +++ /dev/null @@ -1,538 +0,0 @@ -persistency = $persistency; - $this->user = $sqluser; - $this->server = $sqlserver . (($port) ? ':' . $port : ''); - $this->dbname = str_replace('\\', '/', $database); - - // There are three possibilities to connect to an interbase db - if (!$this->server) - { - $use_database = $this->dbname; - } - else if (strpos($this->server, '//') === 0) - { - $use_database = $this->server . $this->dbname; - } - else - { - $use_database = $this->server . ':' . $this->dbname; - } - - if ($this->persistency) - { - if (!function_exists('ibase_pconnect')) - { - $this->connect_error = 'ibase_pconnect function does not exist, is interbase extension installed?'; - return $this->sql_error(''); - } - $this->db_connect_id = @ibase_pconnect($use_database, $this->user, $sqlpassword, false, false, 3); - } - else - { - if (!function_exists('ibase_connect')) - { - $this->connect_error = 'ibase_connect function does not exist, is interbase extension installed?'; - return $this->sql_error(''); - } - $this->db_connect_id = @ibase_connect($use_database, $this->user, $sqlpassword, false, false, 3); - } - - // Do not call ibase_service_attach if connection failed, - // otherwise error message from ibase_(p)connect call will be clobbered. - if ($this->db_connect_id && function_exists('ibase_service_attach') && $this->server) - { - $this->service_handle = @ibase_service_attach($this->server, $this->user, $sqlpassword); - } - else - { - $this->service_handle = false; - } - - return ($this->db_connect_id) ? $this->db_connect_id : $this->sql_error(''); - } - - /** - * Version information about used database - * @param bool $raw if true, only return the fetched sql_server_version - * @param bool $use_cache forced to false for Interbase - * @return string sql server version - */ - function sql_server_info($raw = false, $use_cache = true) - { - /** - * force $use_cache false. I didn't research why the caching code there is no caching code - * but I assume its because the IB extension provides a direct method to access it - * without a query. - */ - - $use_cache = false; - - if ($this->service_handle !== false && function_exists('ibase_server_info')) - { - return @ibase_server_info($this->service_handle, IBASE_SVC_SERVER_VERSION); - } - - return ($raw) ? '2.1' : 'Firebird/Interbase'; - } - - /** - * SQL Transaction - * @access private - */ - function _sql_transaction($status = 'begin') - { - switch ($status) - { - case 'begin': - return true; - break; - - case 'commit': - return @ibase_commit(); - break; - - case 'rollback': - return @ibase_rollback(); - break; - } - - return true; - } - - /** - * Base query method - * - * @param string $query Contains the SQL query which shall be executed - * @param int $cache_ttl Either 0 to avoid caching or the time in seconds which the result shall be kept in cache - * @return mixed When casted to bool the returned value returns true on success and false on failure - * - * @access public - */ - function sql_query($query = '', $cache_ttl = 0) - { - if ($query != '') - { - global $cache; - - // EXPLAIN only in extra debug mode - if (defined('DEBUG')) - { - $this->sql_report('start', $query); - } - - $this->last_query_text = $query; - $this->query_result = ($cache && $cache_ttl) ? $cache->sql_load($query) : false; - $this->sql_add_num_queries($this->query_result); - - if ($this->query_result === false) - { - $array = array(); - // We overcome Firebird's 32767 char limit by binding vars - if (strlen($query) > 32767) - { - if (preg_match('/^(INSERT INTO[^(]++)\\(([^()]+)\\) VALUES[^(]++\\((.*?)\\)$/s', $query, $regs)) - { - if (strlen($regs[3]) > 32767) - { - preg_match_all('/\'(?:[^\']++|\'\')*+\'|[\d-.]+/', $regs[3], $vals, PREG_PATTERN_ORDER); - - $inserts = $vals[0]; - unset($vals); - - foreach ($inserts as $key => $value) - { - if (!empty($value) && $value[0] === "'" && strlen($value) > 32769) // check to see if this thing is greater than the max + 'x2 - { - $inserts[$key] = '?'; - $array[] = str_replace("''", "'", substr($value, 1, -1)); - } - } - - $query = $regs[1] . '(' . $regs[2] . ') VALUES (' . implode(', ', $inserts) . ')'; - } - } - else if (preg_match('/^(UPDATE ([\\w_]++)\\s+SET )([\\w_]++\\s*=\\s*(?:\'(?:[^\']++|\'\')*+\'|\\d+)(?:,\\s*[\\w_]++\\s*=\\s*(?:\'(?:[^\']++|\'\')*+\'|[\d-.]+))*+)\\s+(WHERE.*)$/s', $query, $data)) - { - if (strlen($data[3]) > 32767) - { - $update = $data[1]; - $where = $data[4]; - preg_match_all('/(\\w++)\\s*=\\s*(\'(?:[^\']++|\'\')*+\'|[\d-.]++)/', $data[3], $temp, PREG_SET_ORDER); - unset($data); - - $cols = array(); - foreach ($temp as $value) - { - if (!empty($value[2]) && $value[2][0] === "'" && strlen($value[2]) > 32769) // check to see if this thing is greater than the max + 'x2 - { - $array[] = str_replace("''", "'", substr($value[2], 1, -1)); - $cols[] = $value[1] . '=?'; - } - else - { - $cols[] = $value[1] . '=' . $value[2]; - } - } - - $query = $update . implode(', ', $cols) . ' ' . $where; - unset($cols); - } - } - } - - if (!function_exists('ibase_affected_rows') && (preg_match('/^UPDATE ([\w_]++)\s+SET [\w_]++\s*=\s*(?:\'(?:[^\']++|\'\')*+\'|[\d-.]+)(?:,\s*[\w_]++\s*=\s*(?:\'(?:[^\']++|\'\')*+\'|[\d-.]+))*+\s+(WHERE.*)?$/s', $query, $regs) || preg_match('/^DELETE FROM ([\w_]++)\s*(WHERE\s*.*)?$/s', $query, $regs))) - { - $affected_sql = 'SELECT COUNT(*) as num_rows_affected FROM ' . $regs[1]; - if (!empty($regs[2])) - { - $affected_sql .= ' ' . $regs[2]; - } - - if (!($temp_q_id = @ibase_query($this->db_connect_id, $affected_sql))) - { - return false; - } - - $temp_result = @ibase_fetch_assoc($temp_q_id); - @ibase_free_result($temp_q_id); - - $this->affected_rows = ($temp_result) ? $temp_result['NUM_ROWS_AFFECTED'] : false; - } - - if (sizeof($array)) - { - $p_query = @ibase_prepare($this->db_connect_id, $query); - array_unshift($array, $p_query); - $this->query_result = call_user_func_array('ibase_execute', $array); - unset($array); - - if ($this->query_result === false) - { - $this->sql_error($query); - } - } - else if (($this->query_result = @ibase_query($this->db_connect_id, $query)) === false) - { - $this->sql_error($query); - } - - if (defined('DEBUG')) - { - $this->sql_report('stop', $query); - } - - if (!$this->transaction) - { - if (function_exists('ibase_commit_ret')) - { - @ibase_commit_ret(); - } - else - { - // way cooler than ibase_commit_ret :D - @ibase_query('COMMIT RETAIN;'); - } - } - - if ($cache && $cache_ttl) - { - $this->open_queries[(int) $this->query_result] = $this->query_result; - $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); - } - else if (strpos($query, 'SELECT') === 0 && $this->query_result) - { - $this->open_queries[(int) $this->query_result] = $this->query_result; - } - } - else if (defined('DEBUG')) - { - $this->sql_report('fromcache', $query); - } - } - else - { - return false; - } - - return $this->query_result; - } - - /** - * Build LIMIT query - */ - function _sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) - { - $this->query_result = false; - - $query = 'SELECT FIRST ' . $total . ((!empty($offset)) ? ' SKIP ' . $offset : '') . substr($query, 6); - - return $this->sql_query($query, $cache_ttl); - } - - /** - * Return number of affected rows - */ - function sql_affectedrows() - { - // PHP 5+ function - if (function_exists('ibase_affected_rows')) - { - return ($this->db_connect_id) ? @ibase_affected_rows($this->db_connect_id) : false; - } - else - { - return $this->affected_rows; - } - } - - /** - * Fetch current row - */ - function sql_fetchrow($query_id = false) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if ($cache && $cache->sql_exists($query_id)) - { - return $cache->sql_fetchrow($query_id); - } - - if ($query_id === false) - { - return false; - } - - $row = array(); - $cur_row = @ibase_fetch_object($query_id, IBASE_TEXT); - - if (!$cur_row) - { - return false; - } - - foreach (get_object_vars($cur_row) as $key => $value) - { - $row[strtolower($key)] = (is_string($value)) ? trim(str_replace(array("\\0", "\\n"), array("\0", "\n"), $value)) : $value; - } - - return (sizeof($row)) ? $row : false; - } - - /** - * Get last inserted id after insert statement - */ - function sql_nextid() - { - $query_id = $this->query_result; - - if ($query_id !== false && $this->last_query_text != '') - { - if ($this->query_result && preg_match('#^INSERT[\t\n ]+INTO[\t\n ]+([a-z0-9\_\-]+)#i', $this->last_query_text, $tablename)) - { - $sql = 'SELECT GEN_ID(' . $tablename[1] . '_gen, 0) AS new_id FROM RDB$DATABASE'; - - if (!($temp_q_id = @ibase_query($this->db_connect_id, $sql))) - { - return false; - } - - $temp_result = @ibase_fetch_assoc($temp_q_id); - @ibase_free_result($temp_q_id); - - return ($temp_result) ? $temp_result['NEW_ID'] : false; - } - } - - return false; - } - - /** - * Free sql result - */ - function sql_freeresult($query_id = false) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if ($cache && $cache->sql_exists($query_id)) - { - return $cache->sql_freeresult($query_id); - } - - if (isset($this->open_queries[(int) $query_id])) - { - unset($this->open_queries[(int) $query_id]); - return @ibase_free_result($query_id); - } - - return false; - } - - /** - * Escape string used in sql query - */ - function sql_escape($msg) - { - return str_replace(array("'", "\0"), array("''", ''), $msg); - } - - /** - * Build LIKE expression - * @access private - */ - function _sql_like_expression($expression) - { - return $expression . " ESCAPE '\\'"; - } - - /** - * Build db-specific query data - * @access private - */ - function _sql_custom_build($stage, $data) - { - return $data; - } - - function _sql_bit_and($column_name, $bit, $compare = '') - { - return 'BIN_AND(' . $column_name . ', ' . (1 << $bit) . ')' . (($compare) ? ' ' . $compare : ''); - } - - function _sql_bit_or($column_name, $bit, $compare = '') - { - return 'BIN_OR(' . $column_name . ', ' . (1 << $bit) . ')' . (($compare) ? ' ' . $compare : ''); - } - - /** - * @inheritdoc - */ - function cast_expr_to_bigint($expression) - { - // Precision must be from 1 to 18 - return 'CAST(' . $expression . ' as DECIMAL(18, 0))'; - } - - /** - * @inheritdoc - */ - function cast_expr_to_string($expression) - { - return 'CAST(' . $expression . ' as VARCHAR(255))'; - } - - /** - * return sql error array - * @access private - */ - function _sql_error() - { - // Need special handling here because ibase_errmsg returns - // connection errors, however if the interbase extension - // is not installed then ibase_errmsg does not exist and - // we cannot call it. - if (function_exists('ibase_errmsg')) - { - $msg = @ibase_errmsg(); - if (!$msg) - { - $msg = $this->connect_error; - } - } - else - { - $msg = $this->connect_error; - } - return array( - 'message' => $msg, - 'code' => (@function_exists('ibase_errcode') ? @ibase_errcode() : '') - ); - } - - /** - * Close sql connection - * @access private - */ - function _sql_close() - { - if ($this->service_handle !== false) - { - @ibase_service_detach($this->service_handle); - } - - return @ibase_close($this->db_connect_id); - } - - /** - * Build db-specific report - * @access private - */ - function _sql_report($mode, $query = '') - { - switch ($mode) - { - case 'start': - break; - - case 'fromcache': - $endtime = explode(' ', microtime()); - $endtime = $endtime[0] + $endtime[1]; - - $result = @ibase_query($this->db_connect_id, $query); - while ($void = @ibase_fetch_object($result, IBASE_TEXT)) - { - // Take the time spent on parsing rows into account - } - @ibase_free_result($result); - - $splittime = explode(' ', microtime()); - $splittime = $splittime[0] + $splittime[1]; - - $this->sql_report('record_fromcache', $query, $endtime, $splittime); - - break; - } - } -} diff --git a/phpBB/includes/db/driver/mssql.php b/phpBB/includes/db/driver/mssql.php deleted file mode 100644 index 89c2c2351b..0000000000 --- a/phpBB/includes/db/driver/mssql.php +++ /dev/null @@ -1,472 +0,0 @@ -connect_error = 'mssql_connect function does not exist, is mssql extension installed?'; - return $this->sql_error(''); - } - - $this->persistency = $persistency; - $this->user = $sqluser; - $this->dbname = $database; - - $port_delimiter = (defined('PHP_OS') && substr(PHP_OS, 0, 3) === 'WIN') ? ',' : ':'; - $this->server = $sqlserver . (($port) ? $port_delimiter . $port : ''); - - @ini_set('mssql.charset', 'UTF-8'); - @ini_set('mssql.textlimit', 2147483647); - @ini_set('mssql.textsize', 2147483647); - - $this->db_connect_id = ($this->persistency) ? @mssql_pconnect($this->server, $this->user, $sqlpassword, $new_link) : @mssql_connect($this->server, $this->user, $sqlpassword, $new_link); - - if ($this->db_connect_id && $this->dbname != '') - { - if (!@mssql_select_db($this->dbname, $this->db_connect_id)) - { - @mssql_close($this->db_connect_id); - return false; - } - } - - return ($this->db_connect_id) ? $this->db_connect_id : $this->sql_error(''); - } - - /** - * Version information about used database - * @param bool $raw if true, only return the fetched sql_server_version - * @param bool $use_cache If true, it is safe to retrieve the value from the cache - * @return string sql server version - */ - function sql_server_info($raw = false, $use_cache = true) - { - global $cache; - - if (!$use_cache || empty($cache) || ($this->sql_server_version = $cache->get('mssql_version')) === false) - { - $result_id = @mssql_query("SELECT SERVERPROPERTY('productversion'), SERVERPROPERTY('productlevel'), SERVERPROPERTY('edition')", $this->db_connect_id); - - $row = false; - if ($result_id) - { - $row = @mssql_fetch_assoc($result_id); - @mssql_free_result($result_id); - } - - $this->sql_server_version = ($row) ? trim(implode(' ', $row)) : 0; - - if (!empty($cache) && $use_cache) - { - $cache->put('mssql_version', $this->sql_server_version); - } - } - - if ($raw) - { - return $this->sql_server_version; - } - - return ($this->sql_server_version) ? 'MSSQL
' . $this->sql_server_version : 'MSSQL'; - } - - /** - * {@inheritDoc} - */ - public function sql_concatenate($expr1, $expr2) - { - return $expr1 . ' + ' . $expr2; - } - - /** - * SQL Transaction - * @access private - */ - function _sql_transaction($status = 'begin') - { - switch ($status) - { - case 'begin': - return @mssql_query('BEGIN TRANSACTION', $this->db_connect_id); - break; - - case 'commit': - return @mssql_query('COMMIT TRANSACTION', $this->db_connect_id); - break; - - case 'rollback': - return @mssql_query('ROLLBACK TRANSACTION', $this->db_connect_id); - break; - } - - return true; - } - - /** - * Base query method - * - * @param string $query Contains the SQL query which shall be executed - * @param int $cache_ttl Either 0 to avoid caching or the time in seconds which the result shall be kept in cache - * @return mixed When casted to bool the returned value returns true on success and false on failure - * - * @access public - */ - function sql_query($query = '', $cache_ttl = 0) - { - if ($query != '') - { - global $cache; - - // EXPLAIN only in extra debug mode - if (defined('DEBUG')) - { - $this->sql_report('start', $query); - } - - $this->query_result = ($cache && $cache_ttl) ? $cache->sql_load($query) : false; - $this->sql_add_num_queries($this->query_result); - - if ($this->query_result === false) - { - if (($this->query_result = @mssql_query($query, $this->db_connect_id)) === false) - { - $this->sql_error($query); - } - - if (defined('DEBUG')) - { - $this->sql_report('stop', $query); - } - - if ($cache && $cache_ttl) - { - $this->open_queries[(int) $this->query_result] = $this->query_result; - $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); - } - else if (strpos($query, 'SELECT') === 0 && $this->query_result) - { - $this->open_queries[(int) $this->query_result] = $this->query_result; - } - } - else if (defined('DEBUG')) - { - $this->sql_report('fromcache', $query); - } - } - else - { - return false; - } - - return $this->query_result; - } - - /** - * Build LIMIT query - */ - function _sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) - { - $this->query_result = false; - - // Since TOP is only returning a set number of rows we won't need it if total is set to 0 (return all rows) - if ($total) - { - // We need to grab the total number of rows + the offset number of rows to get the correct result - if (strpos($query, 'SELECT DISTINCT') === 0) - { - $query = 'SELECT DISTINCT TOP ' . ($total + $offset) . ' ' . substr($query, 15); - } - else - { - $query = 'SELECT TOP ' . ($total + $offset) . ' ' . substr($query, 6); - } - } - - $result = $this->sql_query($query, $cache_ttl); - - // Seek by $offset rows - if ($offset) - { - $this->sql_rowseek($offset, $result); - } - - return $result; - } - - /** - * Return number of affected rows - */ - function sql_affectedrows() - { - return ($this->db_connect_id) ? @mssql_rows_affected($this->db_connect_id) : false; - } - - /** - * Fetch current row - */ - function sql_fetchrow($query_id = false) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if ($cache && $cache->sql_exists($query_id)) - { - return $cache->sql_fetchrow($query_id); - } - - if ($query_id === false) - { - return false; - } - - $row = @mssql_fetch_assoc($query_id); - - // I hope i am able to remove this later... hopefully only a PHP or MSSQL bug - if ($row) - { - foreach ($row as $key => $value) - { - $row[$key] = ($value === ' ' || $value === NULL) ? '' : $value; - } - } - - return $row; - } - - /** - * Seek to given row number - * rownum is zero-based - */ - function sql_rowseek($rownum, &$query_id) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if ($cache && $cache->sql_exists($query_id)) - { - return $cache->sql_rowseek($rownum, $query_id); - } - - return ($query_id !== false) ? @mssql_data_seek($query_id, $rownum) : false; - } - - /** - * Get last inserted id after insert statement - */ - function sql_nextid() - { - $result_id = @mssql_query('SELECT SCOPE_IDENTITY()', $this->db_connect_id); - if ($result_id) - { - if ($row = @mssql_fetch_assoc($result_id)) - { - @mssql_free_result($result_id); - return $row['computed']; - } - @mssql_free_result($result_id); - } - - return false; - } - - /** - * Free sql result - */ - function sql_freeresult($query_id = false) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if ($cache && $cache->sql_exists($query_id)) - { - return $cache->sql_freeresult($query_id); - } - - if (isset($this->open_queries[$query_id])) - { - unset($this->open_queries[$query_id]); - return @mssql_free_result($query_id); - } - - return false; - } - - /** - * Escape string used in sql query - */ - function sql_escape($msg) - { - return str_replace(array("'", "\0"), array("''", ''), $msg); - } - - /** - * {@inheritDoc} - */ - function sql_lower_text($column_name) - { - return "LOWER(SUBSTRING($column_name, 1, DATALENGTH($column_name)))"; - } - - /** - * Build LIKE expression - * @access private - */ - function _sql_like_expression($expression) - { - return $expression . " ESCAPE '\\'"; - } - - /** - * return sql error array - * @access private - */ - function _sql_error() - { - if (function_exists('mssql_get_last_message')) - { - $error = array( - 'message' => @mssql_get_last_message(), - 'code' => '', - ); - - // Get error code number - $result_id = @mssql_query('SELECT @@ERROR as code', $this->db_connect_id); - if ($result_id) - { - $row = @mssql_fetch_assoc($result_id); - $error['code'] = $row['code']; - @mssql_free_result($result_id); - } - - // Get full error message if possible - $sql = 'SELECT CAST(description as varchar(255)) as message - FROM master.dbo.sysmessages - WHERE error = ' . $error['code']; - $result_id = @mssql_query($sql); - - if ($result_id) - { - $row = @mssql_fetch_assoc($result_id); - if (!empty($row['message'])) - { - $error['message'] .= '
' . $row['message']; - } - @mssql_free_result($result_id); - } - } - else - { - $error = array( - 'message' => $this->connect_error, - 'code' => '', - ); - } - - return $error; - } - - /** - * Build db-specific query data - * @access private - */ - function _sql_custom_build($stage, $data) - { - return $data; - } - - /** - * Close sql connection - * @access private - */ - function _sql_close() - { - return @mssql_close($this->db_connect_id); - } - - /** - * Build db-specific report - * @access private - */ - function _sql_report($mode, $query = '') - { - switch ($mode) - { - case 'start': - $html_table = false; - @mssql_query('SET SHOWPLAN_TEXT ON;', $this->db_connect_id); - if ($result = @mssql_query($query, $this->db_connect_id)) - { - @mssql_next_result($result); - while ($row = @mssql_fetch_row($result)) - { - $html_table = $this->sql_report('add_select_row', $query, $html_table, $row); - } - } - @mssql_query('SET SHOWPLAN_TEXT OFF;', $this->db_connect_id); - @mssql_free_result($result); - - if ($html_table) - { - $this->html_hold .= ''; - } - break; - - case 'fromcache': - $endtime = explode(' ', microtime()); - $endtime = $endtime[0] + $endtime[1]; - - $result = @mssql_query($query, $this->db_connect_id); - while ($void = @mssql_fetch_assoc($result)) - { - // Take the time spent on parsing rows into account - } - @mssql_free_result($result); - - $splittime = explode(' ', microtime()); - $splittime = $splittime[0] + $splittime[1]; - - $this->sql_report('record_fromcache', $query, $endtime, $splittime); - - break; - } - } -} diff --git a/phpBB/includes/db/driver/mssql_base.php b/phpBB/includes/db/driver/mssql_base.php deleted file mode 100644 index 56c111c871..0000000000 --- a/phpBB/includes/db/driver/mssql_base.php +++ /dev/null @@ -1,65 +0,0 @@ -persistency = $persistency; - $this->user = $sqluser; - $this->dbname = $database; - - $port_delimiter = (defined('PHP_OS') && substr(PHP_OS, 0, 3) === 'WIN') ? ',' : ':'; - $this->server = $sqlserver . (($port) ? $port_delimiter . $port : ''); - - $max_size = @ini_get('odbc.defaultlrl'); - if (!empty($max_size)) - { - $unit = strtolower(substr($max_size, -1, 1)); - $max_size = (int) $max_size; - - if ($unit == 'k') - { - $max_size = floor($max_size / 1024); - } - else if ($unit == 'g') - { - $max_size *= 1024; - } - else if (is_numeric($unit)) - { - $max_size = floor((int) ($max_size . $unit) / 1048576); - } - $max_size = max(8, $max_size) . 'M'; - - @ini_set('odbc.defaultlrl', $max_size); - } - - if ($this->persistency) - { - if (!function_exists('odbc_pconnect')) - { - $this->connect_error = 'odbc_pconnect function does not exist, is odbc extension installed?'; - return $this->sql_error(''); - } - $this->db_connect_id = @odbc_pconnect($this->server, $this->user, $sqlpassword); - } - else - { - if (!function_exists('odbc_connect')) - { - $this->connect_error = 'odbc_connect function does not exist, is odbc extension installed?'; - return $this->sql_error(''); - } - $this->db_connect_id = @odbc_connect($this->server, $this->user, $sqlpassword); - } - - return ($this->db_connect_id) ? $this->db_connect_id : $this->sql_error(''); - } - - /** - * Version information about used database - * @param bool $raw if true, only return the fetched sql_server_version - * @param bool $use_cache If true, it is safe to retrieve the value from the cache - * @return string sql server version - */ - function sql_server_info($raw = false, $use_cache = true) - { - global $cache; - - if (!$use_cache || empty($cache) || ($this->sql_server_version = $cache->get('mssqlodbc_version')) === false) - { - $result_id = @odbc_exec($this->db_connect_id, "SELECT SERVERPROPERTY('productversion'), SERVERPROPERTY('productlevel'), SERVERPROPERTY('edition')"); - - $row = false; - if ($result_id) - { - $row = @odbc_fetch_array($result_id); - @odbc_free_result($result_id); - } - - $this->sql_server_version = ($row) ? trim(implode(' ', $row)) : 0; - - if (!empty($cache) && $use_cache) - { - $cache->put('mssqlodbc_version', $this->sql_server_version); - } - } - - if ($raw) - { - return $this->sql_server_version; - } - - return ($this->sql_server_version) ? 'MSSQL (ODBC)
' . $this->sql_server_version : 'MSSQL (ODBC)'; - } - - /** - * SQL Transaction - * @access private - */ - function _sql_transaction($status = 'begin') - { - switch ($status) - { - case 'begin': - return @odbc_exec($this->db_connect_id, 'BEGIN TRANSACTION'); - break; - - case 'commit': - return @odbc_exec($this->db_connect_id, 'COMMIT TRANSACTION'); - break; - - case 'rollback': - return @odbc_exec($this->db_connect_id, 'ROLLBACK TRANSACTION'); - break; - } - - return true; - } - - /** - * Base query method - * - * @param string $query Contains the SQL query which shall be executed - * @param int $cache_ttl Either 0 to avoid caching or the time in seconds which the result shall be kept in cache - * @return mixed When casted to bool the returned value returns true on success and false on failure - * - * @access public - */ - function sql_query($query = '', $cache_ttl = 0) - { - if ($query != '') - { - global $cache; - - // EXPLAIN only in extra debug mode - if (defined('DEBUG')) - { - $this->sql_report('start', $query); - } - - $this->last_query_text = $query; - $this->query_result = ($cache && $cache_ttl) ? $cache->sql_load($query) : false; - $this->sql_add_num_queries($this->query_result); - - if ($this->query_result === false) - { - if (($this->query_result = @odbc_exec($this->db_connect_id, $query)) === false) - { - $this->sql_error($query); - } - - if (defined('DEBUG')) - { - $this->sql_report('stop', $query); - } - - if ($cache && $cache_ttl) - { - $this->open_queries[(int) $this->query_result] = $this->query_result; - $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); - } - else if (strpos($query, 'SELECT') === 0 && $this->query_result) - { - $this->open_queries[(int) $this->query_result] = $this->query_result; - } - } - else if (defined('DEBUG')) - { - $this->sql_report('fromcache', $query); - } - } - else - { - return false; - } - - return $this->query_result; - } - - /** - * Build LIMIT query - */ - function _sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) - { - $this->query_result = false; - - // Since TOP is only returning a set number of rows we won't need it if total is set to 0 (return all rows) - if ($total) - { - // We need to grab the total number of rows + the offset number of rows to get the correct result - if (strpos($query, 'SELECT DISTINCT') === 0) - { - $query = 'SELECT DISTINCT TOP ' . ($total + $offset) . ' ' . substr($query, 15); - } - else - { - $query = 'SELECT TOP ' . ($total + $offset) . ' ' . substr($query, 6); - } - } - - $result = $this->sql_query($query, $cache_ttl); - - // Seek by $offset rows - if ($offset) - { - $this->sql_rowseek($offset, $result); - } - - return $result; - } - - /** - * Return number of affected rows - */ - function sql_affectedrows() - { - return ($this->db_connect_id) ? @odbc_num_rows($this->query_result) : false; - } - - /** - * Fetch current row - * @note number of bytes returned depends on odbc.defaultlrl php.ini setting. If it is limited to 4K for example only 4K of data is returned max. - */ - function sql_fetchrow($query_id = false) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if ($cache && $cache->sql_exists($query_id)) - { - return $cache->sql_fetchrow($query_id); - } - - return ($query_id !== false) ? @odbc_fetch_array($query_id) : false; - } - - /** - * Get last inserted id after insert statement - */ - function sql_nextid() - { - $result_id = @odbc_exec($this->db_connect_id, 'SELECT @@IDENTITY'); - - if ($result_id) - { - if (@odbc_fetch_array($result_id)) - { - $id = @odbc_result($result_id, 1); - @odbc_free_result($result_id); - return $id; - } - @odbc_free_result($result_id); - } - - return false; - } - - /** - * Free sql result - */ - function sql_freeresult($query_id = false) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if ($cache && $cache->sql_exists($query_id)) - { - return $cache->sql_freeresult($query_id); - } - - if (isset($this->open_queries[(int) $query_id])) - { - unset($this->open_queries[(int) $query_id]); - return @odbc_free_result($query_id); - } - - return false; - } - - /** - * return sql error array - * @access private - */ - function _sql_error() - { - if (function_exists('odbc_errormsg')) - { - $error = array( - 'message' => @odbc_errormsg(), - 'code' => @odbc_error(), - ); - } - else - { - $error = array( - 'message' => $this->connect_error, - 'code' => '', - ); - } - - return $error; - } - - /** - * Close sql connection - * @access private - */ - function _sql_close() - { - return @odbc_close($this->db_connect_id); - } - - /** - * Build db-specific report - * @access private - */ - function _sql_report($mode, $query = '') - { - switch ($mode) - { - case 'start': - break; - - case 'fromcache': - $endtime = explode(' ', microtime()); - $endtime = $endtime[0] + $endtime[1]; - - $result = @odbc_exec($this->db_connect_id, $query); - while ($void = @odbc_fetch_array($result)) - { - // Take the time spent on parsing rows into account - } - @odbc_free_result($result); - - $splittime = explode(' ', microtime()); - $splittime = $splittime[0] + $splittime[1]; - - $this->sql_report('record_fromcache', $query, $endtime, $splittime); - - break; - } - } -} diff --git a/phpBB/includes/db/driver/mssqlnative.php b/phpBB/includes/db/driver/mssqlnative.php deleted file mode 100644 index 28fc88298a..0000000000 --- a/phpBB/includes/db/driver/mssqlnative.php +++ /dev/null @@ -1,613 +0,0 @@ -m_cursor = 0; - $this->m_rows = array(); - $this->m_num_fields = sqlsrv_num_fields($queryresult); - $this->m_field_meta = sqlsrv_field_metadata($queryresult); - - while ($row = sqlsrv_fetch_array($queryresult, SQLSRV_FETCH_ASSOC)) - { - if ($row !== null) - { - foreach($row as $k => $v) - { - if (is_object($v) && method_exists($v, 'format')) - { - $row[$k] = $v->format("Y-m-d\TH:i:s\Z"); - } - } - $this->m_rows[] = $row;//read results into memory, cursors are not supported - } - } - - $this->m_row_count = sizeof($this->m_rows); - } - - private function array_to_obj($array, &$obj) - { - foreach ($array as $key => $value) - { - if (is_array($value)) - { - $obj->$key = new stdClass(); - array_to_obj($value, $obj->$key); - } - else - { - $obj->$key = $value; - } - } - return $obj; - } - - public function fetch($mode = SQLSRV_FETCH_BOTH, $object_class = 'stdClass') - { - if ($this->m_cursor >= $this->m_row_count || $this->m_row_count == 0) - { - return false; - } - - $ret = false; - $arr_num = array(); - - if ($mode == SQLSRV_FETCH_NUMERIC || $mode == SQLSRV_FETCH_BOTH) - { - foreach($this->m_rows[$this->m_cursor] as $key => $value) - { - $arr_num[] = $value; - } - } - - switch ($mode) - { - case SQLSRV_FETCH_ASSOC: - $ret = $this->m_rows[$this->m_cursor]; - break; - case SQLSRV_FETCH_NUMERIC: - $ret = $arr_num; - break; - case 'OBJECT': - $ret = $this->array_to_obj($this->m_rows[$this->m_cursor], $o = new $object_class); - break; - case SQLSRV_FETCH_BOTH: - default: - $ret = $this->m_rows[$this->m_cursor] + $arr_num; - break; - } - $this->m_cursor++; - return $ret; - } - - public function get($pos, $fld) - { - return $this->m_rows[$pos][$fld]; - } - - public function num_rows() - { - return $this->m_row_count; - } - - public function seek($iRow) - { - $this->m_cursor = min($iRow, $this->m_row_count); - } - - public function num_fields() - { - return $this->m_num_fields; - } - - public function field_name($nr) - { - $arr_keys = array_keys($this->m_rows[0]); - return $arr_keys[$nr]; - } - - public function field_type($nr) - { - $i = 0; - $int_type = -1; - $str_type = ''; - - foreach ($this->m_field_meta as $meta) - { - if ($nr == $i) - { - $int_type = $meta['Type']; - break; - } - $i++; - } - - //http://msdn.microsoft.com/en-us/library/cc296183.aspx contains type table - switch ($int_type) - { - case SQLSRV_SQLTYPE_BIGINT: $str_type = 'bigint'; break; - case SQLSRV_SQLTYPE_BINARY: $str_type = 'binary'; break; - case SQLSRV_SQLTYPE_BIT: $str_type = 'bit'; break; - case SQLSRV_SQLTYPE_CHAR: $str_type = 'char'; break; - case SQLSRV_SQLTYPE_DATETIME: $str_type = 'datetime'; break; - case SQLSRV_SQLTYPE_DECIMAL/*($precision, $scale)*/: $str_type = 'decimal'; break; - case SQLSRV_SQLTYPE_FLOAT: $str_type = 'float'; break; - case SQLSRV_SQLTYPE_IMAGE: $str_type = 'image'; break; - case SQLSRV_SQLTYPE_INT: $str_type = 'int'; break; - case SQLSRV_SQLTYPE_MONEY: $str_type = 'money'; break; - case SQLSRV_SQLTYPE_NCHAR/*($charCount)*/: $str_type = 'nchar'; break; - case SQLSRV_SQLTYPE_NUMERIC/*($precision, $scale)*/: $str_type = 'numeric'; break; - case SQLSRV_SQLTYPE_NVARCHAR/*($charCount)*/: $str_type = 'nvarchar'; break; - case SQLSRV_SQLTYPE_NTEXT: $str_type = 'ntext'; break; - case SQLSRV_SQLTYPE_REAL: $str_type = 'real'; break; - case SQLSRV_SQLTYPE_SMALLDATETIME: $str_type = 'smalldatetime'; break; - case SQLSRV_SQLTYPE_SMALLINT: $str_type = 'smallint'; break; - case SQLSRV_SQLTYPE_SMALLMONEY: $str_type = 'smallmoney'; break; - case SQLSRV_SQLTYPE_TEXT: $str_type = 'text'; break; - case SQLSRV_SQLTYPE_TIMESTAMP: $str_type = 'timestamp'; break; - case SQLSRV_SQLTYPE_TINYINT: $str_type = 'tinyint'; break; - case SQLSRV_SQLTYPE_UNIQUEIDENTIFIER: $str_type = 'uniqueidentifier'; break; - case SQLSRV_SQLTYPE_UDT: $str_type = 'UDT'; break; - case SQLSRV_SQLTYPE_VARBINARY/*($byteCount)*/: $str_type = 'varbinary'; break; - case SQLSRV_SQLTYPE_VARCHAR/*($charCount)*/: $str_type = 'varchar'; break; - case SQLSRV_SQLTYPE_XML: $str_type = 'xml'; break; - default: $str_type = $int_type; - } - return $str_type; - } - - public function free() - { - unset($this->m_rows); - return; - } -} - -/** -* @package dbal -*/ -class phpbb_db_driver_mssqlnative extends phpbb_db_driver_mssql_base -{ - var $m_insert_id = NULL; - var $last_query_text = ''; - var $query_options = array(); - var $connect_error = ''; - - /** - * Connect to server - */ - function sql_connect($sqlserver, $sqluser, $sqlpassword, $database, $port = false, $persistency = false, $new_link = false) - { - // Test for driver support, to avoid suppressed fatal error - if (!function_exists('sqlsrv_connect')) - { - $this->connect_error = 'Native MS SQL Server driver for PHP is missing or needs to be updated. Version 1.1 or later is required to install phpBB3. You can download the driver from: http://www.microsoft.com/sqlserver/2005/en/us/PHP-Driver.aspx'; - return $this->sql_error(''); - } - - //set up connection variables - $this->persistency = $persistency; - $this->user = $sqluser; - $this->dbname = $database; - $port_delimiter = (defined('PHP_OS') && substr(PHP_OS, 0, 3) === 'WIN') ? ',' : ':'; - $this->server = $sqlserver . (($port) ? $port_delimiter . $port : ''); - - //connect to database - $this->db_connect_id = sqlsrv_connect($this->server, array( - 'Database' => $this->dbname, - 'UID' => $this->user, - 'PWD' => $sqlpassword - )); - - return ($this->db_connect_id) ? $this->db_connect_id : $this->sql_error(''); - } - - /** - * Version information about used database - * @param bool $raw if true, only return the fetched sql_server_version - * @param bool $use_cache If true, it is safe to retrieve the value from the cache - * @return string sql server version - */ - function sql_server_info($raw = false, $use_cache = true) - { - global $cache; - - if (!$use_cache || empty($cache) || ($this->sql_server_version = $cache->get('mssql_version')) === false) - { - $arr_server_info = sqlsrv_server_info($this->db_connect_id); - $this->sql_server_version = $arr_server_info['SQLServerVersion']; - - if (!empty($cache) && $use_cache) - { - $cache->put('mssql_version', $this->sql_server_version); - } - } - - if ($raw) - { - return $this->sql_server_version; - } - - return ($this->sql_server_version) ? 'MSSQL
' . $this->sql_server_version : 'MSSQL'; - } - - /** - * {@inheritDoc} - */ - function sql_buffer_nested_transactions() - { - return true; - } - - /** - * SQL Transaction - * @access private - */ - function _sql_transaction($status = 'begin') - { - switch ($status) - { - case 'begin': - return sqlsrv_begin_transaction($this->db_connect_id); - break; - - case 'commit': - return sqlsrv_commit($this->db_connect_id); - break; - - case 'rollback': - return sqlsrv_rollback($this->db_connect_id); - break; - } - return true; - } - - /** - * Base query method - * - * @param string $query Contains the SQL query which shall be executed - * @param int $cache_ttl Either 0 to avoid caching or the time in seconds which the result shall be kept in cache - * @return mixed When casted to bool the returned value returns true on success and false on failure - * - * @access public - */ - function sql_query($query = '', $cache_ttl = 0) - { - if ($query != '') - { - global $cache; - - // EXPLAIN only in extra debug mode - if (defined('DEBUG')) - { - $this->sql_report('start', $query); - } - - $this->last_query_text = $query; - $this->query_result = ($cache && $cache_ttl) ? $cache->sql_load($query) : false; - $this->sql_add_num_queries($this->query_result); - - if ($this->query_result === false) - { - if (($this->query_result = @sqlsrv_query($this->db_connect_id, $query, array(), $this->query_options)) === false) - { - $this->sql_error($query); - } - // reset options for next query - $this->query_options = array(); - - if (defined('DEBUG')) - { - $this->sql_report('stop', $query); - } - - if ($cache && $cache_ttl) - { - $this->open_queries[(int) $this->query_result] = $this->query_result; - $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); - } - else if (strpos($query, 'SELECT') === 0 && $this->query_result) - { - $this->open_queries[(int) $this->query_result] = $this->query_result; - } - } - else if (defined('DEBUG')) - { - $this->sql_report('fromcache', $query); - } - } - else - { - return false; - } - return $this->query_result; - } - - /** - * Build LIMIT query - */ - function _sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) - { - $this->query_result = false; - - // total == 0 means all results - not zero results - if ($offset == 0 && $total !== 0) - { - if (strpos($query, "SELECT") === false) - { - $query = "TOP {$total} " . $query; - } - else - { - $query = preg_replace('/SELECT(\s*DISTINCT)?/Dsi', 'SELECT$1 TOP '.$total, $query); - } - } - else if ($offset > 0) - { - $query = preg_replace('/SELECT(\s*DISTINCT)?/Dsi', 'SELECT$1 TOP(10000000) ', $query); - $query = 'SELECT * - FROM (SELECT sub2.*, ROW_NUMBER() OVER(ORDER BY sub2.line2) AS line3 - FROM (SELECT 1 AS line2, sub1.* FROM (' . $query . ') AS sub1) as sub2) AS sub3'; - - if ($total > 0) - { - $query .= ' WHERE line3 BETWEEN ' . ($offset+1) . ' AND ' . ($offset + $total); - } - else - { - $query .= ' WHERE line3 > ' . $offset; - } - } - - $result = $this->sql_query($query, $cache_ttl); - - return $result; - } - - /** - * Return number of affected rows - */ - function sql_affectedrows() - { - return ($this->db_connect_id) ? @sqlsrv_rows_affected($this->query_result) : false; - } - - /** - * Fetch current row - */ - function sql_fetchrow($query_id = false) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if ($cache && $cache->sql_exists($query_id)) - { - return $cache->sql_fetchrow($query_id); - } - - if ($query_id === false) - { - return false; - } - - $row = @sqlsrv_fetch_array($query_id, SQLSRV_FETCH_ASSOC); - - if ($row) - { - foreach ($row as $key => $value) - { - $row[$key] = ($value === ' ' || $value === NULL) ? '' : $value; - } - - // remove helper values from LIMIT queries - if (isset($row['line2'])) - { - unset($row['line2'], $row['line3']); - } - } - return (sizeof($row)) ? $row : false; - } - - /** - * Get last inserted id after insert statement - */ - function sql_nextid() - { - $result_id = @sqlsrv_query($this->db_connect_id, 'SELECT @@IDENTITY'); - - if ($result_id !== false) - { - $row = @sqlsrv_fetch_array($result_id); - $id = $row[0]; - @sqlsrv_free_stmt($result_id); - return $id; - } - else - { - return false; - } - } - - /** - * Free sql result - */ - function sql_freeresult($query_id = false) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if ($cache->sql_exists($query_id)) - { - return $cache->sql_freeresult($query_id); - } - - if (isset($this->open_queries[(int) $query_id])) - { - unset($this->open_queries[(int) $query_id]); - return @sqlsrv_free_stmt($query_id); - } - return false; - } - - /** - * return sql error array - * @access private - */ - function _sql_error() - { - if (function_exists('sqlsrv_errors')) - { - $errors = @sqlsrv_errors(SQLSRV_ERR_ERRORS); - $error_message = ''; - $code = 0; - - if ($errors != null) - { - foreach ($errors as $error) - { - $error_message .= "SQLSTATE: " . $error[ 'SQLSTATE'] . "\n"; - $error_message .= "code: " . $error[ 'code'] . "\n"; - $code = $error['code']; - $error_message .= "message: " . $error[ 'message'] . "\n"; - } - $this->last_error_result = $error_message; - $error = $this->last_error_result; - } - else - { - $error = (isset($this->last_error_result) && $this->last_error_result) ? $this->last_error_result : array(); - } - - $error = array( - 'message' => $error, - 'code' => $code, - ); - } - else - { - $error = array( - 'message' => $this->connect_error, - 'code' => '', - ); - } - - return $error; - } - - /** - * Close sql connection - * @access private - */ - function _sql_close() - { - return @sqlsrv_close($this->db_connect_id); - } - - /** - * Build db-specific report - * @access private - */ - function _sql_report($mode, $query = '') - { - switch ($mode) - { - case 'start': - $html_table = false; - @sqlsrv_query($this->db_connect_id, 'SET SHOWPLAN_TEXT ON;'); - if ($result = @sqlsrv_query($this->db_connect_id, $query)) - { - @sqlsrv_next_result($result); - while ($row = @sqlsrv_fetch_array($result)) - { - $html_table = $this->sql_report('add_select_row', $query, $html_table, $row); - } - } - @sqlsrv_query($this->db_connect_id, 'SET SHOWPLAN_TEXT OFF;'); - @sqlsrv_free_stmt($result); - - if ($html_table) - { - $this->html_hold .= ''; - } - break; - - case 'fromcache': - $endtime = explode(' ', microtime()); - $endtime = $endtime[0] + $endtime[1]; - - $result = @sqlsrv_query($this->db_connect_id, $query); - while ($void = @sqlsrv_fetch_array($result)) - { - // Take the time spent on parsing rows into account - } - @sqlsrv_free_stmt($result); - - $splittime = explode(' ', microtime()); - $splittime = $splittime[0] + $splittime[1]; - - $this->sql_report('record_fromcache', $query, $endtime, $splittime); - - break; - } - } - - /** - * Utility method used to retrieve number of rows - * Emulates mysql_num_rows - * Used in acp_database.php -> write_data_mssqlnative() - * Requires a static or keyset cursor to be definde via - * mssqlnative_set_query_options() - */ - function mssqlnative_num_rows($res) - { - if ($res !== false) - { - return sqlsrv_num_rows($res); - } - else - { - return false; - } - } - - /** - * Allows setting mssqlnative specific query options passed to sqlsrv_query as 4th parameter. - */ - function mssqlnative_set_query_options($options) - { - $this->query_options = $options; - } -} diff --git a/phpBB/includes/db/driver/mysql.php b/phpBB/includes/db/driver/mysql.php deleted file mode 100644 index f3744ac09d..0000000000 --- a/phpBB/includes/db/driver/mysql.php +++ /dev/null @@ -1,472 +0,0 @@ -persistency = $persistency; - $this->user = $sqluser; - $this->server = $sqlserver . (($port) ? ':' . $port : ''); - $this->dbname = $database; - - $this->sql_layer = 'mysql4'; - - if ($this->persistency) - { - if (!function_exists('mysql_pconnect')) - { - $this->connect_error = 'mysql_pconnect function does not exist, is mysql extension installed?'; - return $this->sql_error(''); - } - $this->db_connect_id = @mysql_pconnect($this->server, $this->user, $sqlpassword); - } - else - { - if (!function_exists('mysql_connect')) - { - $this->connect_error = 'mysql_connect function does not exist, is mysql extension installed?'; - return $this->sql_error(''); - } - $this->db_connect_id = @mysql_connect($this->server, $this->user, $sqlpassword, $new_link); - } - - if ($this->db_connect_id && $this->dbname != '') - { - if (@mysql_select_db($this->dbname, $this->db_connect_id)) - { - // Determine what version we are using and if it natively supports UNICODE - if (version_compare($this->sql_server_info(true), '4.1.0', '>=')) - { - @mysql_query("SET NAMES 'utf8'", $this->db_connect_id); - - // enforce strict mode on databases that support it - if (version_compare($this->sql_server_info(true), '5.0.2', '>=')) - { - $result = @mysql_query('SELECT @@session.sql_mode AS sql_mode', $this->db_connect_id); - $row = @mysql_fetch_assoc($result); - @mysql_free_result($result); - $modes = array_map('trim', explode(',', $row['sql_mode'])); - - // TRADITIONAL includes STRICT_ALL_TABLES and STRICT_TRANS_TABLES - if (!in_array('TRADITIONAL', $modes)) - { - if (!in_array('STRICT_ALL_TABLES', $modes)) - { - $modes[] = 'STRICT_ALL_TABLES'; - } - - if (!in_array('STRICT_TRANS_TABLES', $modes)) - { - $modes[] = 'STRICT_TRANS_TABLES'; - } - } - - $mode = implode(',', $modes); - @mysql_query("SET SESSION sql_mode='{$mode}'", $this->db_connect_id); - } - } - else if (version_compare($this->sql_server_info(true), '4.0.0', '<')) - { - $this->sql_layer = 'mysql'; - } - - return $this->db_connect_id; - } - } - - return $this->sql_error(''); - } - - /** - * Version information about used database - * @param bool $raw if true, only return the fetched sql_server_version - * @param bool $use_cache If true, it is safe to retrieve the value from the cache - * @return string sql server version - */ - function sql_server_info($raw = false, $use_cache = true) - { - global $cache; - - if (!$use_cache || empty($cache) || ($this->sql_server_version = $cache->get('mysql_version')) === false) - { - $result = @mysql_query('SELECT VERSION() AS version', $this->db_connect_id); - $row = @mysql_fetch_assoc($result); - @mysql_free_result($result); - - $this->sql_server_version = $row['version']; - - if (!empty($cache) && $use_cache) - { - $cache->put('mysql_version', $this->sql_server_version); - } - } - - return ($raw) ? $this->sql_server_version : 'MySQL ' . $this->sql_server_version; - } - - /** - * SQL Transaction - * @access private - */ - function _sql_transaction($status = 'begin') - { - switch ($status) - { - case 'begin': - return @mysql_query('BEGIN', $this->db_connect_id); - break; - - case 'commit': - return @mysql_query('COMMIT', $this->db_connect_id); - break; - - case 'rollback': - return @mysql_query('ROLLBACK', $this->db_connect_id); - break; - } - - return true; - } - - /** - * Base query method - * - * @param string $query Contains the SQL query which shall be executed - * @param int $cache_ttl Either 0 to avoid caching or the time in seconds which the result shall be kept in cache - * @return mixed When casted to bool the returned value returns true on success and false on failure - * - * @access public - */ - function sql_query($query = '', $cache_ttl = 0) - { - if ($query != '') - { - global $cache; - - // EXPLAIN only in extra debug mode - if (defined('DEBUG')) - { - $this->sql_report('start', $query); - } - - $this->query_result = ($cache && $cache_ttl) ? $cache->sql_load($query) : false; - $this->sql_add_num_queries($this->query_result); - - if ($this->query_result === false) - { - if (($this->query_result = @mysql_query($query, $this->db_connect_id)) === false) - { - $this->sql_error($query); - } - - if (defined('DEBUG')) - { - $this->sql_report('stop', $query); - } - - if ($cache && $cache_ttl) - { - $this->open_queries[(int) $this->query_result] = $this->query_result; - $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); - } - else if (strpos($query, 'SELECT') === 0 && $this->query_result) - { - $this->open_queries[(int) $this->query_result] = $this->query_result; - } - } - else if (defined('DEBUG')) - { - $this->sql_report('fromcache', $query); - } - } - else - { - return false; - } - - return $this->query_result; - } - - /** - * Return number of affected rows - */ - function sql_affectedrows() - { - return ($this->db_connect_id) ? @mysql_affected_rows($this->db_connect_id) : false; - } - - /** - * Fetch current row - */ - function sql_fetchrow($query_id = false) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if ($cache && $cache->sql_exists($query_id)) - { - return $cache->sql_fetchrow($query_id); - } - - return ($query_id !== false) ? @mysql_fetch_assoc($query_id) : false; - } - - /** - * Seek to given row number - * rownum is zero-based - */ - function sql_rowseek($rownum, &$query_id) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if ($cache && $cache->sql_exists($query_id)) - { - return $cache->sql_rowseek($rownum, $query_id); - } - - return ($query_id !== false) ? @mysql_data_seek($query_id, $rownum) : false; - } - - /** - * Get last inserted id after insert statement - */ - function sql_nextid() - { - return ($this->db_connect_id) ? @mysql_insert_id($this->db_connect_id) : false; - } - - /** - * Free sql result - */ - function sql_freeresult($query_id = false) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if ($cache && $cache->sql_exists($query_id)) - { - return $cache->sql_freeresult($query_id); - } - - if (isset($this->open_queries[(int) $query_id])) - { - unset($this->open_queries[(int) $query_id]); - return @mysql_free_result($query_id); - } - - return false; - } - - /** - * Escape string used in sql query - */ - function sql_escape($msg) - { - if (!$this->db_connect_id) - { - return @mysql_real_escape_string($msg); - } - - return @mysql_real_escape_string($msg, $this->db_connect_id); - } - - /** - * return sql error array - * @access private - */ - function _sql_error() - { - if ($this->db_connect_id) - { - $error = array( - 'message' => @mysql_error($this->db_connect_id), - 'code' => @mysql_errno($this->db_connect_id), - ); - } - else if (function_exists('mysql_error')) - { - $error = array( - 'message' => @mysql_error(), - 'code' => @mysql_errno(), - ); - } - else - { - $error = array( - 'message' => $this->connect_error, - 'code' => '', - ); - } - - return $error; - } - - /** - * Close sql connection - * @access private - */ - function _sql_close() - { - return @mysql_close($this->db_connect_id); - } - - /** - * Build db-specific report - * @access private - */ - function _sql_report($mode, $query = '') - { - static $test_prof; - - // current detection method, might just switch to see the existance of INFORMATION_SCHEMA.PROFILING - if ($test_prof === null) - { - $test_prof = false; - if (version_compare($this->sql_server_info(true), '5.0.37', '>=') && version_compare($this->sql_server_info(true), '5.1', '<')) - { - $test_prof = true; - } - } - - switch ($mode) - { - case 'start': - - $explain_query = $query; - if (preg_match('/UPDATE ([a-z0-9_]+).*?WHERE(.*)/s', $query, $m)) - { - $explain_query = 'SELECT * FROM ' . $m[1] . ' WHERE ' . $m[2]; - } - else if (preg_match('/DELETE FROM ([a-z0-9_]+).*?WHERE(.*)/s', $query, $m)) - { - $explain_query = 'SELECT * FROM ' . $m[1] . ' WHERE ' . $m[2]; - } - - if (preg_match('/^SELECT/', $explain_query)) - { - $html_table = false; - - // begin profiling - if ($test_prof) - { - @mysql_query('SET profiling = 1;', $this->db_connect_id); - } - - if ($result = @mysql_query("EXPLAIN $explain_query", $this->db_connect_id)) - { - while ($row = @mysql_fetch_assoc($result)) - { - $html_table = $this->sql_report('add_select_row', $query, $html_table, $row); - } - } - @mysql_free_result($result); - - if ($html_table) - { - $this->html_hold .= ''; - } - - if ($test_prof) - { - $html_table = false; - - // get the last profile - if ($result = @mysql_query('SHOW PROFILE ALL;', $this->db_connect_id)) - { - $this->html_hold .= '
'; - while ($row = @mysql_fetch_assoc($result)) - { - // make HTML safe - if (!empty($row['Source_function'])) - { - $row['Source_function'] = str_replace(array('<', '>'), array('<', '>'), $row['Source_function']); - } - - // remove unsupported features - foreach ($row as $key => $val) - { - if ($val === null) - { - unset($row[$key]); - } - } - $html_table = $this->sql_report('add_select_row', $query, $html_table, $row); - } - } - @mysql_free_result($result); - - if ($html_table) - { - $this->html_hold .= ''; - } - - @mysql_query('SET profiling = 0;', $this->db_connect_id); - } - } - - break; - - case 'fromcache': - $endtime = explode(' ', microtime()); - $endtime = $endtime[0] + $endtime[1]; - - $result = @mysql_query($query, $this->db_connect_id); - while ($void = @mysql_fetch_assoc($result)) - { - // Take the time spent on parsing rows into account - } - @mysql_free_result($result); - - $splittime = explode(' ', microtime()); - $splittime = $splittime[0] + $splittime[1]; - - $this->sql_report('record_fromcache', $query, $endtime, $splittime); - - break; - } - } -} diff --git a/phpBB/includes/db/driver/mysql_base.php b/phpBB/includes/db/driver/mysql_base.php deleted file mode 100644 index ba44ea61aa..0000000000 --- a/phpBB/includes/db/driver/mysql_base.php +++ /dev/null @@ -1,145 +0,0 @@ -query_result = false; - - // if $total is set to 0 we do not want to limit the number of rows - if ($total == 0) - { - // MySQL 4.1+ no longer supports -1 in limit queries - $total = '18446744073709551615'; - } - - $query .= "\n LIMIT " . ((!empty($offset)) ? $offset . ', ' . $total : $total); - - return $this->sql_query($query, $cache_ttl); - } - - /** - * Gets the estimated number of rows in a specified table. - * - * @param string $table_name Table name - * - * @return string Number of rows in $table_name. - * Prefixed with ~ if estimated (otherwise exact). - * - * @access public - */ - function get_estimated_row_count($table_name) - { - $table_status = $this->get_table_status($table_name); - - if (isset($table_status['Engine'])) - { - if ($table_status['Engine'] === 'MyISAM') - { - return $table_status['Rows']; - } - else if ($table_status['Engine'] === 'InnoDB' && $table_status['Rows'] > 100000) - { - return '~' . $table_status['Rows']; - } - } - - return parent::get_row_count($table_name); - } - - /** - * Gets the exact number of rows in a specified table. - * - * @param string $table_name Table name - * - * @return string Exact number of rows in $table_name. - * - * @access public - */ - function get_row_count($table_name) - { - $table_status = $this->get_table_status($table_name); - - if (isset($table_status['Engine']) && $table_status['Engine'] === 'MyISAM') - { - return $table_status['Rows']; - } - - return parent::get_row_count($table_name); - } - - /** - * Gets some information about the specified table. - * - * @param string $table_name Table name - * - * @return array - * - * @access protected - */ - function get_table_status($table_name) - { - $sql = "SHOW TABLE STATUS - LIKE '" . $this->sql_escape($table_name) . "'"; - $result = $this->sql_query($sql); - $table_status = $this->sql_fetchrow($result); - $this->sql_freeresult($result); - - return $table_status; - } - - /** - * Build LIKE expression - * @access private - */ - function _sql_like_expression($expression) - { - return $expression; - } - - /** - * Build db-specific query data - * @access private - */ - function _sql_custom_build($stage, $data) - { - switch ($stage) - { - case 'FROM': - $data = '(' . $data . ')'; - break; - } - - return $data; - } -} diff --git a/phpBB/includes/db/driver/mysqli.php b/phpBB/includes/db/driver/mysqli.php deleted file mode 100644 index 0f7a73ee6e..0000000000 --- a/phpBB/includes/db/driver/mysqli.php +++ /dev/null @@ -1,463 +0,0 @@ -connect_error = 'mysqli_connect function does not exist, is mysqli extension installed?'; - return $this->sql_error(''); - } - - // Mysqli extension supports persistent connection since PHP 5.3.0 - $this->persistency = (version_compare(PHP_VERSION, '5.3.0', '>=')) ? $persistency : false; - $this->user = $sqluser; - - // If persistent connection, set dbhost to localhost when empty and prepend it with 'p:' prefix - $this->server = ($this->persistency) ? 'p:' . (($sqlserver) ? $sqlserver : 'localhost') : $sqlserver; - - $this->dbname = $database; - $port = (!$port) ? NULL : $port; - - // If port is set and it is not numeric, most likely mysqli socket is set. - // Try to map it to the $socket parameter. - $socket = NULL; - if ($port) - { - if (is_numeric($port)) - { - $port = (int) $port; - } - else - { - $socket = $port; - $port = NULL; - } - } - - $this->db_connect_id = @mysqli_connect($this->server, $this->user, $sqlpassword, $this->dbname, $port, $socket); - - if ($this->db_connect_id && $this->dbname != '') - { - @mysqli_query($this->db_connect_id, "SET NAMES 'utf8'"); - - // enforce strict mode on databases that support it - if (version_compare($this->sql_server_info(true), '5.0.2', '>=')) - { - $result = @mysqli_query($this->db_connect_id, 'SELECT @@session.sql_mode AS sql_mode'); - $row = @mysqli_fetch_assoc($result); - @mysqli_free_result($result); - - $modes = array_map('trim', explode(',', $row['sql_mode'])); - - // TRADITIONAL includes STRICT_ALL_TABLES and STRICT_TRANS_TABLES - if (!in_array('TRADITIONAL', $modes)) - { - if (!in_array('STRICT_ALL_TABLES', $modes)) - { - $modes[] = 'STRICT_ALL_TABLES'; - } - - if (!in_array('STRICT_TRANS_TABLES', $modes)) - { - $modes[] = 'STRICT_TRANS_TABLES'; - } - } - - $mode = implode(',', $modes); - @mysqli_query($this->db_connect_id, "SET SESSION sql_mode='{$mode}'"); - } - return $this->db_connect_id; - } - - return $this->sql_error(''); - } - - /** - * Version information about used database - * @param bool $raw if true, only return the fetched sql_server_version - * @param bool $use_cache If true, it is safe to retrieve the value from the cache - * @return string sql server version - */ - function sql_server_info($raw = false, $use_cache = true) - { - global $cache; - - if (!$use_cache || empty($cache) || ($this->sql_server_version = $cache->get('mysqli_version')) === false) - { - $result = @mysqli_query($this->db_connect_id, 'SELECT VERSION() AS version'); - $row = @mysqli_fetch_assoc($result); - @mysqli_free_result($result); - - $this->sql_server_version = $row['version']; - - if (!empty($cache) && $use_cache) - { - $cache->put('mysqli_version', $this->sql_server_version); - } - } - - return ($raw) ? $this->sql_server_version : 'MySQL(i) ' . $this->sql_server_version; - } - - /** - * SQL Transaction - * @access private - */ - function _sql_transaction($status = 'begin') - { - switch ($status) - { - case 'begin': - return @mysqli_autocommit($this->db_connect_id, false); - break; - - case 'commit': - $result = @mysqli_commit($this->db_connect_id); - @mysqli_autocommit($this->db_connect_id, true); - return $result; - break; - - case 'rollback': - $result = @mysqli_rollback($this->db_connect_id); - @mysqli_autocommit($this->db_connect_id, true); - return $result; - break; - } - - return true; - } - - /** - * Base query method - * - * @param string $query Contains the SQL query which shall be executed - * @param int $cache_ttl Either 0 to avoid caching or the time in seconds which the result shall be kept in cache - * @return mixed When casted to bool the returned value returns true on success and false on failure - * - * @access public - */ - function sql_query($query = '', $cache_ttl = 0) - { - if ($query != '') - { - global $cache; - - // EXPLAIN only in extra debug mode - if (defined('DEBUG')) - { - $this->sql_report('start', $query); - } - - $this->query_result = ($cache && $cache_ttl) ? $cache->sql_load($query) : false; - $this->sql_add_num_queries($this->query_result); - - if ($this->query_result === false) - { - if (($this->query_result = @mysqli_query($this->db_connect_id, $query)) === false) - { - $this->sql_error($query); - } - - if (defined('DEBUG')) - { - $this->sql_report('stop', $query); - } - - if ($cache && $cache_ttl) - { - $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); - } - } - else if (defined('DEBUG')) - { - $this->sql_report('fromcache', $query); - } - } - else - { - return false; - } - - return $this->query_result; - } - - /** - * Return number of affected rows - */ - function sql_affectedrows() - { - return ($this->db_connect_id) ? @mysqli_affected_rows($this->db_connect_id) : false; - } - - /** - * Fetch current row - */ - function sql_fetchrow($query_id = false) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if ($cache && !is_object($query_id) && $cache->sql_exists($query_id)) - { - return $cache->sql_fetchrow($query_id); - } - - if ($query_id !== false) - { - $result = @mysqli_fetch_assoc($query_id); - return $result !== null ? $result : false; - } - - return false; - } - - /** - * Seek to given row number - * rownum is zero-based - */ - function sql_rowseek($rownum, &$query_id) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if ($cache && !is_object($query_id) && $cache->sql_exists($query_id)) - { - return $cache->sql_rowseek($rownum, $query_id); - } - - return ($query_id !== false) ? @mysqli_data_seek($query_id, $rownum) : false; - } - - /** - * Get last inserted id after insert statement - */ - function sql_nextid() - { - return ($this->db_connect_id) ? @mysqli_insert_id($this->db_connect_id) : false; - } - - /** - * Free sql result - */ - function sql_freeresult($query_id = false) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if ($cache && !is_object($query_id) && $cache->sql_exists($query_id)) - { - return $cache->sql_freeresult($query_id); - } - - return @mysqli_free_result($query_id); - } - - /** - * Escape string used in sql query - */ - function sql_escape($msg) - { - return @mysqli_real_escape_string($this->db_connect_id, $msg); - } - - /** - * return sql error array - * @access private - */ - function _sql_error() - { - if ($this->db_connect_id) - { - $error = array( - 'message' => @mysqli_error($this->db_connect_id), - 'code' => @mysqli_errno($this->db_connect_id) - ); - } - else if (function_exists('mysqli_connect_error')) - { - $error = array( - 'message' => @mysqli_connect_error(), - 'code' => @mysqli_connect_errno(), - ); - } - else - { - $error = array( - 'message' => $this->connect_error, - 'code' => '', - ); - } - - return $error; - } - - /** - * Close sql connection - * @access private - */ - function _sql_close() - { - return @mysqli_close($this->db_connect_id); - } - - /** - * Build db-specific report - * @access private - */ - function _sql_report($mode, $query = '') - { - static $test_prof; - - // current detection method, might just switch to see the existance of INFORMATION_SCHEMA.PROFILING - if ($test_prof === null) - { - $test_prof = false; - if (strpos(mysqli_get_server_info($this->db_connect_id), 'community') !== false) - { - $ver = mysqli_get_server_version($this->db_connect_id); - if ($ver >= 50037 && $ver < 50100) - { - $test_prof = true; - } - } - } - - switch ($mode) - { - case 'start': - - $explain_query = $query; - if (preg_match('/UPDATE ([a-z0-9_]+).*?WHERE(.*)/s', $query, $m)) - { - $explain_query = 'SELECT * FROM ' . $m[1] . ' WHERE ' . $m[2]; - } - else if (preg_match('/DELETE FROM ([a-z0-9_]+).*?WHERE(.*)/s', $query, $m)) - { - $explain_query = 'SELECT * FROM ' . $m[1] . ' WHERE ' . $m[2]; - } - - if (preg_match('/^SELECT/', $explain_query)) - { - $html_table = false; - - // begin profiling - if ($test_prof) - { - @mysqli_query($this->db_connect_id, 'SET profiling = 1;'); - } - - if ($result = @mysqli_query($this->db_connect_id, "EXPLAIN $explain_query")) - { - while ($row = @mysqli_fetch_assoc($result)) - { - $html_table = $this->sql_report('add_select_row', $query, $html_table, $row); - } - } - @mysqli_free_result($result); - - if ($html_table) - { - $this->html_hold .= ''; - } - - if ($test_prof) - { - $html_table = false; - - // get the last profile - if ($result = @mysqli_query($this->db_connect_id, 'SHOW PROFILE ALL;')) - { - $this->html_hold .= '
'; - while ($row = @mysqli_fetch_assoc($result)) - { - // make HTML safe - if (!empty($row['Source_function'])) - { - $row['Source_function'] = str_replace(array('<', '>'), array('<', '>'), $row['Source_function']); - } - - // remove unsupported features - foreach ($row as $key => $val) - { - if ($val === null) - { - unset($row[$key]); - } - } - $html_table = $this->sql_report('add_select_row', $query, $html_table, $row); - } - } - @mysqli_free_result($result); - - if ($html_table) - { - $this->html_hold .= ''; - } - - @mysqli_query($this->db_connect_id, 'SET profiling = 0;'); - } - } - - break; - - case 'fromcache': - $endtime = explode(' ', microtime()); - $endtime = $endtime[0] + $endtime[1]; - - $result = @mysqli_query($this->db_connect_id, $query); - while ($void = @mysqli_fetch_assoc($result)) - { - // Take the time spent on parsing rows into account - } - @mysqli_free_result($result); - - $splittime = explode(' ', microtime()); - $splittime = $splittime[0] + $splittime[1]; - - $this->sql_report('record_fromcache', $query, $endtime, $splittime); - - break; - } - } -} diff --git a/phpBB/includes/db/driver/oracle.php b/phpBB/includes/db/driver/oracle.php deleted file mode 100644 index e21e07055d..0000000000 --- a/phpBB/includes/db/driver/oracle.php +++ /dev/null @@ -1,803 +0,0 @@ -persistency = $persistency; - $this->user = $sqluser; - $this->server = $sqlserver . (($port) ? ':' . $port : ''); - $this->dbname = $database; - - $connect = $database; - - // support for "easy connect naming" - if ($sqlserver !== '' && $sqlserver !== '/') - { - if (substr($sqlserver, -1, 1) == '/') - { - $sqlserver == substr($sqlserver, 0, -1); - } - $connect = $sqlserver . (($port) ? ':' . $port : '') . '/' . $database; - } - - if ($new_link) - { - if (!function_exists('ocinlogon')) - { - $this->connect_error = 'ocinlogon function does not exist, is oci extension installed?'; - return $this->sql_error(''); - } - $this->db_connect_id = @ocinlogon($this->user, $sqlpassword, $connect, 'UTF8'); - } - else if ($this->persistency) - { - if (!function_exists('ociplogon')) - { - $this->connect_error = 'ociplogon function does not exist, is oci extension installed?'; - return $this->sql_error(''); - } - $this->db_connect_id = @ociplogon($this->user, $sqlpassword, $connect, 'UTF8'); - } - else - { - if (!function_exists('ocilogon')) - { - $this->connect_error = 'ocilogon function does not exist, is oci extension installed?'; - return $this->sql_error(''); - } - $this->db_connect_id = @ocilogon($this->user, $sqlpassword, $connect, 'UTF8'); - } - - return ($this->db_connect_id) ? $this->db_connect_id : $this->sql_error(''); - } - - /** - * Version information about used database - * @param bool $raw if true, only return the fetched sql_server_version - * @param bool $use_cache forced to false for Oracle - * @return string sql server version - */ - function sql_server_info($raw = false, $use_cache = true) - { - /** - * force $use_cache false. I didn't research why the caching code below is commented out - * but I assume its because the Oracle extension provides a direct method to access it - * without a query. - */ - - $use_cache = false; -/* - global $cache; - - if (empty($cache) || ($this->sql_server_version = $cache->get('oracle_version')) === false) - { - $result = @ociparse($this->db_connect_id, 'SELECT * FROM v$version WHERE banner LIKE \'Oracle%\''); - @ociexecute($result, OCI_DEFAULT); - @ocicommit($this->db_connect_id); - - $row = array(); - @ocifetchinto($result, $row, OCI_ASSOC + OCI_RETURN_NULLS); - @ocifreestatement($result); - $this->sql_server_version = trim($row['BANNER']); - - $cache->put('oracle_version', $this->sql_server_version); - } -*/ - $this->sql_server_version = @ociserverversion($this->db_connect_id); - - return $this->sql_server_version; - } - - /** - * SQL Transaction - * @access private - */ - function _sql_transaction($status = 'begin') - { - switch ($status) - { - case 'begin': - return true; - break; - - case 'commit': - return @ocicommit($this->db_connect_id); - break; - - case 'rollback': - return @ocirollback($this->db_connect_id); - break; - } - - return true; - } - - /** - * Oracle specific code to handle the fact that it does not compare columns properly - * @access private - */ - function _rewrite_col_compare($args) - { - if (sizeof($args) == 4) - { - if ($args[2] == '=') - { - return '(' . $args[0] . ' OR (' . $args[1] . ' is NULL AND ' . $args[3] . ' is NULL))'; - } - else if ($args[2] == '<>') - { - // really just a fancy way of saying foo <> bar or (foo is NULL XOR bar is NULL) but SQL has no XOR :P - return '(' . $args[0] . ' OR ((' . $args[1] . ' is NULL AND ' . $args[3] . ' is NOT NULL) OR (' . $args[1] . ' is NOT NULL AND ' . $args[3] . ' is NULL)))'; - } - } - else - { - return $this->_rewrite_where($args[0]); - } - } - - /** - * Oracle specific code to handle it's lack of sanity - * @access private - */ - function _rewrite_where($where_clause) - { - preg_match_all('/\s*(AND|OR)?\s*([\w_.()]++)\s*(?:(=|<[=>]?|>=?|LIKE)\s*((?>\'(?>[^\']++|\'\')*+\'|[\d-.()]+))|((NOT )?IN\s*\((?>\'(?>[^\']++|\'\')*+\',? ?|[\d-.]+,? ?)*+\)))/', $where_clause, $result, PREG_SET_ORDER); - $out = ''; - foreach ($result as $val) - { - if (!isset($val[5])) - { - if ($val[4] !== "''") - { - $out .= $val[0]; - } - else - { - $out .= ' ' . $val[1] . ' ' . $val[2]; - if ($val[3] == '=') - { - $out .= ' is NULL'; - } - else if ($val[3] == '<>') - { - $out .= ' is NOT NULL'; - } - } - } - else - { - $in_clause = array(); - $sub_exp = substr($val[5], strpos($val[5], '(') + 1, -1); - $extra = false; - preg_match_all('/\'(?>[^\']++|\'\')*+\'|[\d-.]++/', $sub_exp, $sub_vals, PREG_PATTERN_ORDER); - $i = 0; - foreach ($sub_vals[0] as $sub_val) - { - // two things: - // 1) This determines if an empty string was in the IN clausing, making us turn it into a NULL comparison - // 2) This fixes the 1000 list limit that Oracle has (ORA-01795) - if ($sub_val !== "''") - { - $in_clause[(int) $i++/1000][] = $sub_val; - } - else - { - $extra = true; - } - } - if (!$extra && $i < 1000) - { - $out .= $val[0]; - } - else - { - $out .= ' ' . $val[1] . '('; - $in_array = array(); - - // constuct each IN() clause - foreach ($in_clause as $in_values) - { - $in_array[] = $val[2] . ' ' . (isset($val[6]) ? $val[6] : '') . 'IN(' . implode(', ', $in_values) . ')'; - } - - // Join the IN() clauses against a few ORs (IN is just a nicer OR anyway) - $out .= implode(' OR ', $in_array); - - // handle the empty string case - if ($extra) - { - $out .= ' OR ' . $val[2] . ' is ' . (isset($val[6]) ? $val[6] : '') . 'NULL'; - } - $out .= ')'; - - unset($in_array, $in_clause); - } - } - } - - return $out; - } - - /** - * Base query method - * - * @param string $query Contains the SQL query which shall be executed - * @param int $cache_ttl Either 0 to avoid caching or the time in seconds which the result shall be kept in cache - * @return mixed When casted to bool the returned value returns true on success and false on failure - * - * @access public - */ - function sql_query($query = '', $cache_ttl = 0) - { - if ($query != '') - { - global $cache; - - // EXPLAIN only in extra debug mode - if (defined('DEBUG')) - { - $this->sql_report('start', $query); - } - - $this->last_query_text = $query; - $this->query_result = ($cache && $cache_ttl) ? $cache->sql_load($query) : false; - $this->sql_add_num_queries($this->query_result); - - if ($this->query_result === false) - { - $in_transaction = false; - if (!$this->transaction) - { - $this->sql_transaction('begin'); - } - else - { - $in_transaction = true; - } - - $array = array(); - - // We overcome Oracle's 4000 char limit by binding vars - if (strlen($query) > 4000) - { - if (preg_match('/^(INSERT INTO[^(]++)\\(([^()]+)\\) VALUES[^(]++\\((.*?)\\)$/sU', $query, $regs)) - { - if (strlen($regs[3]) > 4000) - { - $cols = explode(', ', $regs[2]); - - preg_match_all('/\'(?:[^\']++|\'\')*+\'|[\d-.]+/', $regs[3], $vals, PREG_PATTERN_ORDER); - -/* The code inside this comment block breaks clob handling, but does allow the - database restore script to work. If you want to allow no posts longer than 4KB - and/or need the db restore script, uncomment this. - - - if (sizeof($cols) !== sizeof($vals)) - { - // Try to replace some common data we know is from our restore script or from other sources - $regs[3] = str_replace("'||chr(47)||'", '/', $regs[3]); - $_vals = explode(', ', $regs[3]); - - $vals = array(); - $is_in_val = false; - $i = 0; - $string = ''; - - foreach ($_vals as $value) - { - if (strpos($value, "'") === false && !$is_in_val) - { - $vals[$i++] = $value; - continue; - } - - if (substr($value, -1) === "'") - { - $vals[$i] = $string . (($is_in_val) ? ', ' : '') . $value; - $string = ''; - $is_in_val = false; - - if ($vals[$i][0] !== "'") - { - $vals[$i] = "''" . $vals[$i]; - } - $i++; - continue; - } - else - { - $string .= (($is_in_val) ? ', ' : '') . $value; - $is_in_val = true; - } - } - - if ($string) - { - // New value if cols != value - $vals[(sizeof($cols) !== sizeof($vals)) ? $i : $i - 1] .= $string; - } - - $vals = array(0 => $vals); - } -*/ - - $inserts = $vals[0]; - unset($vals); - - foreach ($inserts as $key => $value) - { - if (!empty($value) && $value[0] === "'" && strlen($value) > 4002) // check to see if this thing is greater than the max + 'x2 - { - $inserts[$key] = ':' . strtoupper($cols[$key]); - $array[$inserts[$key]] = str_replace("''", "'", substr($value, 1, -1)); - } - } - - $query = $regs[1] . '(' . $regs[2] . ') VALUES (' . implode(', ', $inserts) . ')'; - } - } - else if (preg_match_all('/^(UPDATE [\\w_]++\\s+SET )([\\w_]++\\s*=\\s*(?:\'(?:[^\']++|\'\')*+\'|[\d-.]+)(?:,\\s*[\\w_]++\\s*=\\s*(?:\'(?:[^\']++|\'\')*+\'|[\d-.]+))*+)\\s+(WHERE.*)$/s', $query, $data, PREG_SET_ORDER)) - { - if (strlen($data[0][2]) > 4000) - { - $update = $data[0][1]; - $where = $data[0][3]; - preg_match_all('/([\\w_]++)\\s*=\\s*(\'(?:[^\']++|\'\')*+\'|[\d-.]++)/', $data[0][2], $temp, PREG_SET_ORDER); - unset($data); - - $cols = array(); - foreach ($temp as $value) - { - if (!empty($value[2]) && $value[2][0] === "'" && strlen($value[2]) > 4002) // check to see if this thing is greater than the max + 'x2 - { - $cols[] = $value[1] . '=:' . strtoupper($value[1]); - $array[$value[1]] = str_replace("''", "'", substr($value[2], 1, -1)); - } - else - { - $cols[] = $value[1] . '=' . $value[2]; - } - } - - $query = $update . implode(', ', $cols) . ' ' . $where; - unset($cols); - } - } - } - - switch (substr($query, 0, 6)) - { - case 'DELETE': - if (preg_match('/^(DELETE FROM [\w_]++ WHERE)((?:\s*(?:AND|OR)?\s*[\w_]+\s*(?:(?:=|<>)\s*(?>\'(?>[^\']++|\'\')*+\'|[\d-.]+)|(?:NOT )?IN\s*\((?>\'(?>[^\']++|\'\')*+\',? ?|[\d-.]+,? ?)*+\)))*+)$/', $query, $regs)) - { - $query = $regs[1] . $this->_rewrite_where($regs[2]); - unset($regs); - } - break; - - case 'UPDATE': - if (preg_match('/^(UPDATE [\\w_]++\\s+SET [\\w_]+\s*=\s*(?:\'(?:[^\']++|\'\')*+\'|[\d-.]++|:\w++)(?:, [\\w_]+\s*=\s*(?:\'(?:[^\']++|\'\')*+\'|[\d-.]++|:\w++))*+\\s+WHERE)(.*)$/s', $query, $regs)) - { - $query = $regs[1] . $this->_rewrite_where($regs[2]); - unset($regs); - } - break; - - case 'SELECT': - $query = preg_replace_callback('/([\w_.]++)\s*(?:(=|<>)\s*(?>\'(?>[^\']++|\'\')*+\'|[\d-.]++|([\w_.]++))|(?:NOT )?IN\s*\((?>\'(?>[^\']++|\'\')*+\',? ?|[\d-.]++,? ?)*+\))/', array($this, '_rewrite_col_compare'), $query); - break; - } - - $this->query_result = @ociparse($this->db_connect_id, $query); - - foreach ($array as $key => $value) - { - @ocibindbyname($this->query_result, $key, $array[$key], -1); - } - - $success = @ociexecute($this->query_result, OCI_DEFAULT); - - if (!$success) - { - $this->sql_error($query); - $this->query_result = false; - } - else - { - if (!$in_transaction) - { - $this->sql_transaction('commit'); - } - } - - if (defined('DEBUG')) - { - $this->sql_report('stop', $query); - } - - if ($cache && $cache_ttl) - { - $this->open_queries[(int) $this->query_result] = $this->query_result; - $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); - } - else if (strpos($query, 'SELECT') === 0 && $this->query_result) - { - $this->open_queries[(int) $this->query_result] = $this->query_result; - } - } - else if (defined('DEBUG')) - { - $this->sql_report('fromcache', $query); - } - } - else - { - return false; - } - - return $this->query_result; - } - - /** - * Build LIMIT query - */ - function _sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) - { - $this->query_result = false; - - $query = 'SELECT * FROM (SELECT /*+ FIRST_ROWS */ rownum AS xrownum, a.* FROM (' . $query . ') a WHERE rownum <= ' . ($offset + $total) . ') WHERE xrownum >= ' . $offset; - - return $this->sql_query($query, $cache_ttl); - } - - /** - * Return number of affected rows - */ - function sql_affectedrows() - { - return ($this->query_result) ? @ocirowcount($this->query_result) : false; - } - - /** - * Fetch current row - */ - function sql_fetchrow($query_id = false) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if ($cache && $cache->sql_exists($query_id)) - { - return $cache->sql_fetchrow($query_id); - } - - if ($query_id !== false) - { - $row = array(); - $result = @ocifetchinto($query_id, $row, OCI_ASSOC + OCI_RETURN_NULLS); - - if (!$result || !$row) - { - return false; - } - - $result_row = array(); - foreach ($row as $key => $value) - { - // Oracle treats empty strings as null - if (is_null($value)) - { - $value = ''; - } - - // OCI->CLOB? - if (is_object($value)) - { - $value = $value->load(); - } - - $result_row[strtolower($key)] = $value; - } - - return $result_row; - } - - return false; - } - - /** - * Seek to given row number - * rownum is zero-based - */ - function sql_rowseek($rownum, &$query_id) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if ($cache && $cache->sql_exists($query_id)) - { - return $cache->sql_rowseek($rownum, $query_id); - } - - if ($query_id === false) - { - return false; - } - - // Reset internal pointer - @ociexecute($query_id, OCI_DEFAULT); - - // We do not fetch the row for rownum == 0 because then the next resultset would be the second row - for ($i = 0; $i < $rownum; $i++) - { - if (!$this->sql_fetchrow($query_id)) - { - return false; - } - } - - return true; - } - - /** - * Get last inserted id after insert statement - */ - function sql_nextid() - { - $query_id = $this->query_result; - - if ($query_id !== false && $this->last_query_text != '') - { - if (preg_match('#^INSERT[\t\n ]+INTO[\t\n ]+([a-z0-9\_\-]+)#is', $this->last_query_text, $tablename)) - { - $query = 'SELECT ' . $tablename[1] . '_seq.currval FROM DUAL'; - $stmt = @ociparse($this->db_connect_id, $query); - @ociexecute($stmt, OCI_DEFAULT); - - $temp_result = @ocifetchinto($stmt, $temp_array, OCI_ASSOC + OCI_RETURN_NULLS); - @ocifreestatement($stmt); - - if ($temp_result) - { - return $temp_array['CURRVAL']; - } - else - { - return false; - } - } - } - - return false; - } - - /** - * Free sql result - */ - function sql_freeresult($query_id = false) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if ($cache && $cache->sql_exists($query_id)) - { - return $cache->sql_freeresult($query_id); - } - - if (isset($this->open_queries[(int) $query_id])) - { - unset($this->open_queries[(int) $query_id]); - return @ocifreestatement($query_id); - } - - return false; - } - - /** - * Escape string used in sql query - */ - function sql_escape($msg) - { - return str_replace(array("'", "\0"), array("''", ''), $msg); - } - - /** - * Build LIKE expression - * @access private - */ - function _sql_like_expression($expression) - { - return $expression . " ESCAPE '\\'"; - } - - function _sql_custom_build($stage, $data) - { - return $data; - } - - function _sql_bit_and($column_name, $bit, $compare = '') - { - return 'BITAND(' . $column_name . ', ' . (1 << $bit) . ')' . (($compare) ? ' ' . $compare : ''); - } - - function _sql_bit_or($column_name, $bit, $compare = '') - { - return 'BITOR(' . $column_name . ', ' . (1 << $bit) . ')' . (($compare) ? ' ' . $compare : ''); - } - - /** - * return sql error array - * @access private - */ - function _sql_error() - { - if (function_exists('ocierror')) - { - $error = @ocierror(); - $error = (!$error) ? @ocierror($this->query_result) : $error; - $error = (!$error) ? @ocierror($this->db_connect_id) : $error; - - if ($error) - { - $this->last_error_result = $error; - } - else - { - $error = (isset($this->last_error_result) && $this->last_error_result) ? $this->last_error_result : array(); - } - } - else - { - $error = array( - 'message' => $this->connect_error, - 'code' => '', - ); - } - - return $error; - } - - /** - * Close sql connection - * @access private - */ - function _sql_close() - { - return @ocilogoff($this->db_connect_id); - } - - /** - * Build db-specific report - * @access private - */ - function _sql_report($mode, $query = '') - { - switch ($mode) - { - case 'start': - - $html_table = false; - - // Grab a plan table, any will do - $sql = "SELECT table_name - FROM USER_TABLES - WHERE table_name LIKE '%PLAN_TABLE%'"; - $stmt = ociparse($this->db_connect_id, $sql); - ociexecute($stmt); - $result = array(); - - if (ocifetchinto($stmt, $result, OCI_ASSOC + OCI_RETURN_NULLS)) - { - $table = $result['TABLE_NAME']; - - // This is the statement_id that will allow us to track the plan - $statement_id = substr(md5($query), 0, 30); - - // Remove any stale plans - $stmt2 = ociparse($this->db_connect_id, "DELETE FROM $table WHERE statement_id='$statement_id'"); - ociexecute($stmt2); - ocifreestatement($stmt2); - - // Explain the plan - $sql = "EXPLAIN PLAN - SET STATEMENT_ID = '$statement_id' - FOR $query"; - $stmt2 = ociparse($this->db_connect_id, $sql); - ociexecute($stmt2); - ocifreestatement($stmt2); - - // Get the data from the plan - $sql = "SELECT operation, options, object_name, object_type, cardinality, cost - FROM plan_table - START WITH id = 0 AND statement_id = '$statement_id' - CONNECT BY PRIOR id = parent_id - AND statement_id = '$statement_id'"; - $stmt2 = ociparse($this->db_connect_id, $sql); - ociexecute($stmt2); - - $row = array(); - while (ocifetchinto($stmt2, $row, OCI_ASSOC + OCI_RETURN_NULLS)) - { - $html_table = $this->sql_report('add_select_row', $query, $html_table, $row); - } - - ocifreestatement($stmt2); - - // Remove the plan we just made, we delete them on request anyway - $stmt2 = ociparse($this->db_connect_id, "DELETE FROM $table WHERE statement_id='$statement_id'"); - ociexecute($stmt2); - ocifreestatement($stmt2); - } - - ocifreestatement($stmt); - - if ($html_table) - { - $this->html_hold .= ''; - } - - break; - - case 'fromcache': - $endtime = explode(' ', microtime()); - $endtime = $endtime[0] + $endtime[1]; - - $result = @ociparse($this->db_connect_id, $query); - $success = @ociexecute($result, OCI_DEFAULT); - $row = array(); - - while (@ocifetchinto($result, $row, OCI_ASSOC + OCI_RETURN_NULLS)) - { - // Take the time spent on parsing rows into account - } - @ocifreestatement($result); - - $splittime = explode(' ', microtime()); - $splittime = $splittime[0] + $splittime[1]; - - $this->sql_report('record_fromcache', $query, $endtime, $splittime); - - break; - } - } -} diff --git a/phpBB/includes/db/driver/postgres.php b/phpBB/includes/db/driver/postgres.php deleted file mode 100644 index 14854d179d..0000000000 --- a/phpBB/includes/db/driver/postgres.php +++ /dev/null @@ -1,491 +0,0 @@ -dbname = $database; - if (strpos($database, '.') !== false) - { - list($database, $schema) = explode('.', $database); - } - $connect_string .= "dbname=$database"; - } - - $this->persistency = $persistency; - - if ($this->persistency) - { - if (!function_exists('pg_pconnect')) - { - $this->connect_error = 'pg_pconnect function does not exist, is pgsql extension installed?'; - return $this->sql_error(''); - } - $collector = new phpbb_error_collector; - $collector->install(); - $this->db_connect_id = (!$new_link) ? @pg_pconnect($connect_string) : @pg_pconnect($connect_string, PGSQL_CONNECT_FORCE_NEW); - } - else - { - if (!function_exists('pg_connect')) - { - $this->connect_error = 'pg_connect function does not exist, is pgsql extension installed?'; - return $this->sql_error(''); - } - $collector = new phpbb_error_collector; - $collector->install(); - $this->db_connect_id = (!$new_link) ? @pg_connect($connect_string) : @pg_connect($connect_string, PGSQL_CONNECT_FORCE_NEW); - } - - $collector->uninstall(); - - if ($this->db_connect_id) - { - if (version_compare($this->sql_server_info(true), '8.2', '>=')) - { - $this->multi_insert = true; - } - - if ($schema !== '') - { - @pg_query($this->db_connect_id, 'SET search_path TO ' . $schema); - } - return $this->db_connect_id; - } - - $this->connect_error = $collector->format_errors(); - return $this->sql_error(''); - } - - /** - * Version information about used database - * @param bool $raw if true, only return the fetched sql_server_version - * @param bool $use_cache If true, it is safe to retrieve the value from the cache - * @return string sql server version - */ - function sql_server_info($raw = false, $use_cache = true) - { - global $cache; - - if (!$use_cache || empty($cache) || ($this->sql_server_version = $cache->get('pgsql_version')) === false) - { - $query_id = @pg_query($this->db_connect_id, 'SELECT VERSION() AS version'); - $row = @pg_fetch_assoc($query_id, null); - @pg_free_result($query_id); - - $this->sql_server_version = (!empty($row['version'])) ? trim(substr($row['version'], 10)) : 0; - - if (!empty($cache) && $use_cache) - { - $cache->put('pgsql_version', $this->sql_server_version); - } - } - - return ($raw) ? $this->sql_server_version : 'PostgreSQL ' . $this->sql_server_version; - } - - /** - * SQL Transaction - * @access private - */ - function _sql_transaction($status = 'begin') - { - switch ($status) - { - case 'begin': - return @pg_query($this->db_connect_id, 'BEGIN'); - break; - - case 'commit': - return @pg_query($this->db_connect_id, 'COMMIT'); - break; - - case 'rollback': - return @pg_query($this->db_connect_id, 'ROLLBACK'); - break; - } - - return true; - } - - /** - * Base query method - * - * @param string $query Contains the SQL query which shall be executed - * @param int $cache_ttl Either 0 to avoid caching or the time in seconds which the result shall be kept in cache - * @return mixed When casted to bool the returned value returns true on success and false on failure - * - * @access public - */ - function sql_query($query = '', $cache_ttl = 0) - { - if ($query != '') - { - global $cache; - - // EXPLAIN only in extra debug mode - if (defined('DEBUG')) - { - $this->sql_report('start', $query); - } - - $this->last_query_text = $query; - $this->query_result = ($cache && $cache_ttl) ? $cache->sql_load($query) : false; - $this->sql_add_num_queries($this->query_result); - - if ($this->query_result === false) - { - if (($this->query_result = @pg_query($this->db_connect_id, $query)) === false) - { - $this->sql_error($query); - } - - if (defined('DEBUG')) - { - $this->sql_report('stop', $query); - } - - if ($cache && $cache_ttl) - { - $this->open_queries[(int) $this->query_result] = $this->query_result; - $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); - } - else if (strpos($query, 'SELECT') === 0 && $this->query_result) - { - $this->open_queries[(int) $this->query_result] = $this->query_result; - } - } - else if (defined('DEBUG')) - { - $this->sql_report('fromcache', $query); - } - } - else - { - return false; - } - - return $this->query_result; - } - - /** - * Build db-specific query data - * @access private - */ - function _sql_custom_build($stage, $data) - { - return $data; - } - - /** - * Build LIMIT query - */ - function _sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) - { - $this->query_result = false; - - // if $total is set to 0 we do not want to limit the number of rows - if ($total == 0) - { - $total = 'ALL'; - } - - $query .= "\n LIMIT $total OFFSET $offset"; - - return $this->sql_query($query, $cache_ttl); - } - - /** - * Return number of affected rows - */ - function sql_affectedrows() - { - return ($this->query_result) ? @pg_affected_rows($this->query_result) : false; - } - - /** - * Fetch current row - */ - function sql_fetchrow($query_id = false) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if ($cache && $cache->sql_exists($query_id)) - { - return $cache->sql_fetchrow($query_id); - } - - return ($query_id !== false) ? @pg_fetch_assoc($query_id, null) : false; - } - - /** - * Seek to given row number - * rownum is zero-based - */ - function sql_rowseek($rownum, &$query_id) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if ($cache && $cache->sql_exists($query_id)) - { - return $cache->sql_rowseek($rownum, $query_id); - } - - return ($query_id !== false) ? @pg_result_seek($query_id, $rownum) : false; - } - - /** - * Get last inserted id after insert statement - */ - function sql_nextid() - { - $query_id = $this->query_result; - - if ($query_id !== false && $this->last_query_text != '') - { - if (preg_match("/^INSERT[\t\n ]+INTO[\t\n ]+([a-z0-9\_\-]+)/is", $this->last_query_text, $tablename)) - { - $query = "SELECT currval('" . $tablename[1] . "_seq') AS last_value"; - $temp_q_id = @pg_query($this->db_connect_id, $query); - - if (!$temp_q_id) - { - return false; - } - - $temp_result = @pg_fetch_assoc($temp_q_id, NULL); - @pg_free_result($query_id); - - return ($temp_result) ? $temp_result['last_value'] : false; - } - } - - return false; - } - - /** - * Free sql result - */ - function sql_freeresult($query_id = false) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if ($cache && $cache->sql_exists($query_id)) - { - return $cache->sql_freeresult($query_id); - } - - if (isset($this->open_queries[(int) $query_id])) - { - unset($this->open_queries[(int) $query_id]); - return @pg_free_result($query_id); - } - - return false; - } - - /** - * Escape string used in sql query - * Note: Do not use for bytea values if we may use them at a later stage - */ - function sql_escape($msg) - { - return @pg_escape_string($msg); - } - - /** - * Build LIKE expression - * @access private - */ - function _sql_like_expression($expression) - { - return $expression; - } - - /** - * @inheritdoc - */ - function cast_expr_to_bigint($expression) - { - return 'CAST(' . $expression . ' as DECIMAL(255, 0))'; - } - - /** - * @inheritdoc - */ - function cast_expr_to_string($expression) - { - return 'CAST(' . $expression . ' as VARCHAR(255))'; - } - - /** - * return sql error array - * @access private - */ - function _sql_error() - { - // pg_last_error only works when there is an established connection. - // Connection errors have to be tracked by us manually. - if ($this->db_connect_id) - { - $message = @pg_last_error($this->db_connect_id); - } - else - { - $message = $this->connect_error; - } - - return array( - 'message' => $message, - 'code' => '' - ); - } - - /** - * Close sql connection - * @access private - */ - function _sql_close() - { - return @pg_close($this->db_connect_id); - } - - /** - * Build db-specific report - * @access private - */ - function _sql_report($mode, $query = '') - { - switch ($mode) - { - case 'start': - - $explain_query = $query; - if (preg_match('/UPDATE ([a-z0-9_]+).*?WHERE(.*)/s', $query, $m)) - { - $explain_query = 'SELECT * FROM ' . $m[1] . ' WHERE ' . $m[2]; - } - else if (preg_match('/DELETE FROM ([a-z0-9_]+).*?WHERE(.*)/s', $query, $m)) - { - $explain_query = 'SELECT * FROM ' . $m[1] . ' WHERE ' . $m[2]; - } - - if (preg_match('/^SELECT/', $explain_query)) - { - $html_table = false; - - if ($result = @pg_query($this->db_connect_id, "EXPLAIN $explain_query")) - { - while ($row = @pg_fetch_assoc($result, NULL)) - { - $html_table = $this->sql_report('add_select_row', $query, $html_table, $row); - } - } - @pg_free_result($result); - - if ($html_table) - { - $this->html_hold .= ''; - } - } - - break; - - case 'fromcache': - $endtime = explode(' ', microtime()); - $endtime = $endtime[0] + $endtime[1]; - - $result = @pg_query($this->db_connect_id, $query); - while ($void = @pg_fetch_assoc($result, NULL)) - { - // Take the time spent on parsing rows into account - } - @pg_free_result($result); - - $splittime = explode(' ', microtime()); - $splittime = $splittime[0] + $splittime[1]; - - $this->sql_report('record_fromcache', $query, $endtime, $splittime); - - break; - } - } -} diff --git a/phpBB/includes/db/driver/sqlite.php b/phpBB/includes/db/driver/sqlite.php deleted file mode 100644 index 7188f0daa2..0000000000 --- a/phpBB/includes/db/driver/sqlite.php +++ /dev/null @@ -1,365 +0,0 @@ -persistency = $persistency; - $this->user = $sqluser; - $this->server = $sqlserver . (($port) ? ':' . $port : ''); - $this->dbname = $database; - - $error = ''; - if ($this->persistency) - { - if (!function_exists('sqlite_popen')) - { - $this->connect_error = 'sqlite_popen function does not exist, is sqlite extension installed?'; - return $this->sql_error(''); - } - $this->db_connect_id = @sqlite_popen($this->server, 0666, $error); - } - else - { - if (!function_exists('sqlite_open')) - { - $this->connect_error = 'sqlite_open function does not exist, is sqlite extension installed?'; - return $this->sql_error(''); - } - $this->db_connect_id = @sqlite_open($this->server, 0666, $error); - } - - if ($this->db_connect_id) - { - @sqlite_query('PRAGMA short_column_names = 1', $this->db_connect_id); -// @sqlite_query('PRAGMA encoding = "UTF-8"', $this->db_connect_id); - } - - return ($this->db_connect_id) ? true : array('message' => $error); - } - - /** - * Version information about used database - * @param bool $raw if true, only return the fetched sql_server_version - * @param bool $use_cache if true, it is safe to retrieve the stored value from the cache - * @return string sql server version - */ - function sql_server_info($raw = false, $use_cache = true) - { - global $cache; - - if (!$use_cache || empty($cache) || ($this->sql_server_version = $cache->get('sqlite_version')) === false) - { - $result = @sqlite_query('SELECT sqlite_version() AS version', $this->db_connect_id); - $row = @sqlite_fetch_array($result, SQLITE_ASSOC); - - $this->sql_server_version = (!empty($row['version'])) ? $row['version'] : 0; - - if (!empty($cache) && $use_cache) - { - $cache->put('sqlite_version', $this->sql_server_version); - } - } - - return ($raw) ? $this->sql_server_version : 'SQLite ' . $this->sql_server_version; - } - - /** - * SQL Transaction - * @access private - */ - function _sql_transaction($status = 'begin') - { - switch ($status) - { - case 'begin': - return @sqlite_query('BEGIN', $this->db_connect_id); - break; - - case 'commit': - return @sqlite_query('COMMIT', $this->db_connect_id); - break; - - case 'rollback': - return @sqlite_query('ROLLBACK', $this->db_connect_id); - break; - } - - return true; - } - - /** - * Base query method - * - * @param string $query Contains the SQL query which shall be executed - * @param int $cache_ttl Either 0 to avoid caching or the time in seconds which the result shall be kept in cache - * @return mixed When casted to bool the returned value returns true on success and false on failure - * - * @access public - */ - function sql_query($query = '', $cache_ttl = 0) - { - if ($query != '') - { - global $cache; - - // EXPLAIN only in extra debug mode - if (defined('DEBUG')) - { - $this->sql_report('start', $query); - } - - $this->query_result = ($cache && $cache_ttl) ? $cache->sql_load($query) : false; - $this->sql_add_num_queries($this->query_result); - - if ($this->query_result === false) - { - if (($this->query_result = @sqlite_query($query, $this->db_connect_id)) === false) - { - $this->sql_error($query); - } - - if (defined('DEBUG')) - { - $this->sql_report('stop', $query); - } - - if ($cache && $cache_ttl) - { - $this->open_queries[(int) $this->query_result] = $this->query_result; - $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); - } - else if (strpos($query, 'SELECT') === 0 && $this->query_result) - { - $this->open_queries[(int) $this->query_result] = $this->query_result; - } - } - else if (defined('DEBUG')) - { - $this->sql_report('fromcache', $query); - } - } - else - { - return false; - } - - return $this->query_result; - } - - /** - * Build LIMIT query - */ - function _sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) - { - $this->query_result = false; - - // if $total is set to 0 we do not want to limit the number of rows - if ($total == 0) - { - $total = -1; - } - - $query .= "\n LIMIT " . ((!empty($offset)) ? $offset . ', ' . $total : $total); - - return $this->sql_query($query, $cache_ttl); - } - - /** - * Return number of affected rows - */ - function sql_affectedrows() - { - return ($this->db_connect_id) ? @sqlite_changes($this->db_connect_id) : false; - } - - /** - * Fetch current row - */ - function sql_fetchrow($query_id = false) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if ($cache && $cache->sql_exists($query_id)) - { - return $cache->sql_fetchrow($query_id); - } - - return ($query_id !== false) ? @sqlite_fetch_array($query_id, SQLITE_ASSOC) : false; - } - - /** - * Seek to given row number - * rownum is zero-based - */ - function sql_rowseek($rownum, &$query_id) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if ($cache && $cache->sql_exists($query_id)) - { - return $cache->sql_rowseek($rownum, $query_id); - } - - return ($query_id !== false) ? @sqlite_seek($query_id, $rownum) : false; - } - - /** - * Get last inserted id after insert statement - */ - function sql_nextid() - { - return ($this->db_connect_id) ? @sqlite_last_insert_rowid($this->db_connect_id) : false; - } - - /** - * Free sql result - */ - function sql_freeresult($query_id = false) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if ($cache && $cache->sql_exists($query_id)) - { - return $cache->sql_freeresult($query_id); - } - - return true; - } - - /** - * Escape string used in sql query - */ - function sql_escape($msg) - { - return @sqlite_escape_string($msg); - } - - /** - * Correctly adjust LIKE expression for special characters - * For SQLite an underscore is a not-known character... this may change with SQLite3 - */ - function sql_like_expression($expression) - { - // Unlike LIKE, GLOB is case sensitive (unfortunatly). SQLite users need to live with it! - // We only catch * and ? here, not the character map possible on file globbing. - $expression = str_replace(array(chr(0) . '_', chr(0) . '%'), array(chr(0) . '?', chr(0) . '*'), $expression); - - $expression = str_replace(array('?', '*'), array("\?", "\*"), $expression); - $expression = str_replace(array(chr(0) . "\?", chr(0) . "\*"), array('?', '*'), $expression); - - return 'GLOB \'' . $this->sql_escape($expression) . '\''; - } - - /** - * return sql error array - * @access private - */ - function _sql_error() - { - if (function_exists('sqlite_error_string')) - { - $error = array( - 'message' => @sqlite_error_string(@sqlite_last_error($this->db_connect_id)), - 'code' => @sqlite_last_error($this->db_connect_id), - ); - } - else - { - $error = array( - 'message' => $this->connect_error, - 'code' => '', - ); - } - - return $error; - } - - /** - * Build db-specific query data - * @access private - */ - function _sql_custom_build($stage, $data) - { - return $data; - } - - /** - * Close sql connection - * @access private - */ - function _sql_close() - { - return @sqlite_close($this->db_connect_id); - } - - /** - * Build db-specific report - * @access private - */ - function _sql_report($mode, $query = '') - { - switch ($mode) - { - case 'start': - break; - - case 'fromcache': - $endtime = explode(' ', microtime()); - $endtime = $endtime[0] + $endtime[1]; - - $result = @sqlite_query($query, $this->db_connect_id); - while ($void = @sqlite_fetch_array($result, SQLITE_ASSOC)) - { - // Take the time spent on parsing rows into account - } - - $splittime = explode(' ', microtime()); - $splittime = $splittime[0] + $splittime[1]; - - $this->sql_report('record_fromcache', $query, $endtime, $splittime); - - break; - } - } -} diff --git a/phpBB/includes/db/migration/data/30x/3_0_1.php b/phpBB/includes/db/migration/data/30x/3_0_1.php deleted file mode 100644 index c996a0138a..0000000000 --- a/phpBB/includes/db/migration/data/30x/3_0_1.php +++ /dev/null @@ -1,28 +0,0 @@ -config['version'], '3.0.1', '>='); - } - - static public function depends_on() - { - return array('phpbb_db_migration_data_30x_3_0_1_rc1'); - } - - public function update_data() - { - return array( - array('config.update', array('version', '3.0.1')), - ); - } -} diff --git a/phpBB/includes/db/migration/data/30x/3_0_10.php b/phpBB/includes/db/migration/data/30x/3_0_10.php deleted file mode 100644 index 122f93d6b4..0000000000 --- a/phpBB/includes/db/migration/data/30x/3_0_10.php +++ /dev/null @@ -1,28 +0,0 @@ -config['version'], '3.0.10', '>='); - } - - static public function depends_on() - { - return array('phpbb_db_migration_data_30x_3_0_10_rc3'); - } - - public function update_data() - { - return array( - array('config.update', array('version', '3.0.10')), - ); - } -} diff --git a/phpBB/includes/db/migration/data/30x/3_0_10_rc1.php b/phpBB/includes/db/migration/data/30x/3_0_10_rc1.php deleted file mode 100644 index 0ed05812dc..0000000000 --- a/phpBB/includes/db/migration/data/30x/3_0_10_rc1.php +++ /dev/null @@ -1,30 +0,0 @@ -config['version'], '3.0.10-rc1', '>='); - } - - static public function depends_on() - { - return array('phpbb_db_migration_data_30x_3_0_9'); - } - - public function update_data() - { - return array( - array('config.add', array('email_max_chunk_size', 50)), - - array('config.update', array('version', '3.0.10-rc1')), - ); - } -} diff --git a/phpBB/includes/db/migration/data/30x/3_0_10_rc2.php b/phpBB/includes/db/migration/data/30x/3_0_10_rc2.php deleted file mode 100644 index b14b3b00aa..0000000000 --- a/phpBB/includes/db/migration/data/30x/3_0_10_rc2.php +++ /dev/null @@ -1,28 +0,0 @@ -config['version'], '3.0.10-rc2', '>='); - } - - static public function depends_on() - { - return array('phpbb_db_migration_data_30x_3_0_10_rc1'); - } - - public function update_data() - { - return array( - array('config.update', array('version', '3.0.10-rc2')), - ); - } -} diff --git a/phpBB/includes/db/migration/data/30x/3_0_10_rc3.php b/phpBB/includes/db/migration/data/30x/3_0_10_rc3.php deleted file mode 100644 index 473057d65d..0000000000 --- a/phpBB/includes/db/migration/data/30x/3_0_10_rc3.php +++ /dev/null @@ -1,28 +0,0 @@ -config['version'], '3.0.10-rc3', '>='); - } - - static public function depends_on() - { - return array('phpbb_db_migration_data_30x_3_0_10_rc2'); - } - - public function update_data() - { - return array( - array('config.update', array('version', '3.0.10-rc3')), - ); - } -} diff --git a/phpBB/includes/db/migration/data/30x/3_0_11.php b/phpBB/includes/db/migration/data/30x/3_0_11.php deleted file mode 100644 index e063c699cc..0000000000 --- a/phpBB/includes/db/migration/data/30x/3_0_11.php +++ /dev/null @@ -1,28 +0,0 @@ -config['version'], '3.0.11', '>='); - } - - static public function depends_on() - { - return array('phpbb_db_migration_data_30x_3_0_11_rc2'); - } - - public function update_data() - { - return array( - array('config.update', array('version', '3.0.11')), - ); - } -} diff --git a/phpBB/includes/db/migration/data/30x/3_0_11_rc1.php b/phpBB/includes/db/migration/data/30x/3_0_11_rc1.php deleted file mode 100644 index dddfc0e0e7..0000000000 --- a/phpBB/includes/db/migration/data/30x/3_0_11_rc1.php +++ /dev/null @@ -1,95 +0,0 @@ -config['version'], '3.0.11-rc1', '>='); - } - - static public function depends_on() - { - return array('phpbb_db_migration_data_30x_3_0_10'); - } - - public function update_data() - { - return array( - array('custom', array(array(&$this, 'cleanup_deactivated_styles'))), - array('custom', array(array(&$this, 'delete_orphan_private_messages'))), - - array('config.update', array('version', '3.0.11-rc1')), - ); - } - - public function cleanup_deactivated_styles() - { - // Updates users having current style a deactivated one - $sql = 'SELECT style_id - FROM ' . STYLES_TABLE . ' - WHERE style_active = 0'; - $result = $this->sql_query($sql); - - $deactivated_style_ids = array(); - while ($style_id = $this->db->sql_fetchfield('style_id', false, $result)) - { - $deactivated_style_ids[] = (int) $style_id; - } - $this->db->sql_freeresult($result); - - if (!empty($deactivated_style_ids)) - { - $sql = 'UPDATE ' . USERS_TABLE . ' - SET user_style = ' . (int) $this->config['default_style'] .' - WHERE ' . $this->db->sql_in_set('user_style', $deactivated_style_ids); - $this->sql_query($sql); - } - } - - public function delete_orphan_private_messages() - { - // Delete orphan private messages - $batch_size = 500; - - $sql_array = array( - 'SELECT' => 'p.msg_id', - 'FROM' => array( - PRIVMSGS_TABLE => 'p', - ), - 'LEFT_JOIN' => array( - array( - 'FROM' => array(PRIVMSGS_TO_TABLE => 't'), - 'ON' => 'p.msg_id = t.msg_id', - ), - ), - 'WHERE' => 't.user_id IS NULL', - ); - $sql = $this->db->sql_build_query('SELECT', $sql_array); - - $result = $this->db->sql_query_limit($sql, $batch_size); - - $delete_pms = array(); - while ($row = $this->db->sql_fetchrow($result)) - { - $delete_pms[] = (int) $row['msg_id']; - } - $this->db->sql_freeresult($result); - - if (!empty($delete_pms)) - { - $sql = 'DELETE FROM ' . PRIVMSGS_TABLE . ' - WHERE ' . $this->db->sql_in_set('msg_id', $delete_pms); - $this->sql_query($sql); - - // Return false to have the Migrator call this function again - return false; - } - } -} diff --git a/phpBB/includes/db/migration/data/30x/3_0_11_rc2.php b/phpBB/includes/db/migration/data/30x/3_0_11_rc2.php deleted file mode 100644 index fac8523e8c..0000000000 --- a/phpBB/includes/db/migration/data/30x/3_0_11_rc2.php +++ /dev/null @@ -1,50 +0,0 @@ -config['version'], '3.0.11-rc2', '>='); - } - - static public function depends_on() - { - return array('phpbb_db_migration_data_30x_3_0_11_rc1'); - } - - public function update_schema() - { - return array( - 'add_columns' => array( - $this->table_prefix . 'profile_fields' => array( - 'field_show_novalue' => array('BOOL', 0), - ), - ), - ); - } - - public function revert_schema() - { - return array( - 'drop_columns' => array( - $this->table_prefix . 'profile_fields' => array( - 'field_show_novalue', - ), - ), - ); - } - - public function update_data() - { - return array( - array('config.update', array('version', '3.0.11-rc2')), - ); - } -} diff --git a/phpBB/includes/db/migration/data/30x/3_0_12_rc1.php b/phpBB/includes/db/migration/data/30x/3_0_12_rc1.php deleted file mode 100644 index 6a31a51201..0000000000 --- a/phpBB/includes/db/migration/data/30x/3_0_12_rc1.php +++ /dev/null @@ -1,123 +0,0 @@ -config['version'], '3.0.12-rc1', '>='); - } - - static public function depends_on() - { - return array('phpbb_db_migration_data_30x_3_0_11'); - } - - public function update_data() - { - return array( - array('custom', array(array(&$this, 'update_module_auth'))), - array('custom', array(array(&$this, 'update_bots'))), - array('custom', array(array(&$this, 'disable_bots_from_receiving_pms'))), - - array('config.update', array('version', '3.0.12-rc1')), - ); - } - - public function disable_bots_from_receiving_pms() - { - // Disable receiving pms for bots - $sql = 'SELECT user_id - FROM ' . BOTS_TABLE; - $result = $this->db->sql_query($sql); - - $bot_user_ids = array(); - while ($row = $this->db->sql_fetchrow($result)) - { - $bot_user_ids[] = (int) $row['user_id']; - } - $this->db->sql_freeresult($result); - - if (!empty($bot_user_ids)) - { - $sql = 'UPDATE ' . USERS_TABLE . ' - SET user_allow_pm = 0 - WHERE ' . $this->db->sql_in_set('user_id', $bot_user_ids); - $this->sql_query($sql); - } - } - - public function update_module_auth() - { - $sql = 'UPDATE ' . MODULES_TABLE . ' - SET module_auth = \'acl_u_sig\' - WHERE module_class = \'ucp\' - AND module_basename = \'profile\' - AND module_mode = \'signature\''; - $this->sql_query($sql); - } - - public function update_bots() - { - // Update bots - if (!function_exists('user_delete')) - { - include($this->phpbb_root_path . 'includes/functions_user.' . $this->php_ext); - } - - $bots_updates = array( - // Bot Deletions - 'NG-Search [Bot]' => false, - 'Nutch/CVS [Bot]' => false, - 'OmniExplorer [Bot]' => false, - 'Seekport [Bot]' => false, - 'Synoo [Bot]' => false, - 'WiseNut [Bot]' => false, - - // Bot Updates - // Bot name to bot user agent map - 'Baidu [Spider]' => 'Baiduspider', - 'Exabot [Bot]' => 'Exabot', - 'Voyager [Bot]' => 'voyager/', - 'W3C [Validator]' => 'W3C_Validator', - ); - - foreach ($bots_updates as $bot_name => $bot_agent) - { - $sql = 'SELECT user_id - FROM ' . USERS_TABLE . ' - WHERE user_type = ' . USER_IGNORE . " - AND username_clean = '" . $this->db->sql_escape(utf8_clean_string($bot_name)) . "'"; - $result = $this->db->sql_query($sql); - $bot_user_id = (int) $this->db->sql_fetchfield('user_id'); - $this->db->sql_freeresult($result); - - if ($bot_user_id) - { - if ($bot_agent === false) - { - $sql = 'DELETE FROM ' . BOTS_TABLE . " - WHERE user_id = $bot_user_id"; - $this->sql_query($sql); - - user_delete('remove', $bot_user_id); - } - else - { - $sql = 'UPDATE ' . BOTS_TABLE . " - SET bot_agent = '" . $this->db->sql_escape($bot_agent) . "' - WHERE user_id = $bot_user_id"; - $this->sql_query($sql); - } - } - } - } -} diff --git a/phpBB/includes/db/migration/data/30x/3_0_1_rc1.php b/phpBB/includes/db/migration/data/30x/3_0_1_rc1.php deleted file mode 100644 index 562ccf077c..0000000000 --- a/phpBB/includes/db/migration/data/30x/3_0_1_rc1.php +++ /dev/null @@ -1,108 +0,0 @@ -config['version'], '3.0.1-rc1', '>='); - } - - public function update_schema() - { - return array( - 'add_columns' => array( - $this->table_prefix . 'forums' => array( - 'display_subforum_list' => array('BOOL', 1), - ), - $this->table_prefix . 'sessions' => array( - 'session_forum_id' => array('UINT', 0), - ), - ), - 'drop_keys' => array( - $this->table_prefix . 'groups' => array( - 'group_legend', - ), - ), - 'add_index' => array( - $this->table_prefix . 'sessions' => array( - 'session_forum_id' => array('session_forum_id'), - ), - $this->table_prefix . 'groups' => array( - 'group_legend_name' => array('group_legend', 'group_name'), - ), - ), - ); - } - - public function revert_schema() - { - return array( - 'drop_columns' => array( - $this->table_prefix . 'forums' => array( - 'display_subforum_list', - ), - $this->table_prefix . 'sessions' => array( - 'session_forum_id', - ), - ), - 'add_index' => array( - $this->table_prefix . 'groups' => array( - 'group_legend' => array('group_legend'), - ), - ), - 'drop_keys' => array( - $this->table_prefix . 'sessions' => array( - 'session_forum_id', - ), - $this->table_prefix . 'groups' => array( - 'group_legend_name', - ), - ), - ); - } - - public function update_data() - { - return array( - array('custom', array(array(&$this, 'fix_unset_last_view_time'))), - array('custom', array(array(&$this, 'reset_smiley_size'))), - - array('config.update', array('version', '3.0.1-rc1')), - ); - } - - public function fix_unset_last_view_time() - { - $sql = 'UPDATE ' . $this->table_prefix . "topics - SET topic_last_view_time = topic_last_post_time - WHERE topic_last_view_time = 0"; - $this->sql_query($sql); - } - - public function reset_smiley_size() - { - // Update smiley sizes - $smileys = array('icon_e_surprised.gif', 'icon_eek.gif', 'icon_cool.gif', 'icon_lol.gif', 'icon_mad.gif', 'icon_razz.gif', 'icon_redface.gif', 'icon_cry.gif', 'icon_evil.gif', 'icon_twisted.gif', 'icon_rolleyes.gif', 'icon_exclaim.gif', 'icon_question.gif', 'icon_idea.gif', 'icon_arrow.gif', 'icon_neutral.gif', 'icon_mrgreen.gif', 'icon_e_ugeek.gif'); - - foreach ($smileys as $smiley) - { - if (file_exists($this->phpbb_root_path . 'images/smilies/' . $smiley)) - { - list($width, $height) = getimagesize($this->phpbb_root_path . 'images/smilies/' . $smiley); - - $sql = 'UPDATE ' . SMILIES_TABLE . ' - SET smiley_width = ' . $width . ', smiley_height = ' . $height . " - WHERE smiley_url = '" . $this->db->sql_escape($smiley) . "'"; - - $this->sql_query($sql); - } - } - } -} diff --git a/phpBB/includes/db/migration/data/30x/3_0_2.php b/phpBB/includes/db/migration/data/30x/3_0_2.php deleted file mode 100644 index eed5acef82..0000000000 --- a/phpBB/includes/db/migration/data/30x/3_0_2.php +++ /dev/null @@ -1,28 +0,0 @@ -config['version'], '3.0.2', '>='); - } - - static public function depends_on() - { - return array('phpbb_db_migration_data_30x_3_0_2_rc2'); - } - - public function update_data() - { - return array( - array('config.update', array('version', '3.0.2')), - ); - } -} diff --git a/phpBB/includes/db/migration/data/30x/3_0_2_rc1.php b/phpBB/includes/db/migration/data/30x/3_0_2_rc1.php deleted file mode 100644 index a960e90765..0000000000 --- a/phpBB/includes/db/migration/data/30x/3_0_2_rc1.php +++ /dev/null @@ -1,32 +0,0 @@ -config['version'], '3.0.2-rc1', '>='); - } - - static public function depends_on() - { - return array('phpbb_db_migration_data_30x_3_0_1'); - } - - public function update_data() - { - return array( - array('config.add', array('referer_validation', '1')), - array('config.add', array('check_attachment_content', '1')), - array('config.add', array('mime_triggers', 'body|head|html|img|plaintext|a href|pre|script|table|title')), - - array('config.update', array('version', '3.0.2-rc1')), - ); - } -} diff --git a/phpBB/includes/db/migration/data/30x/3_0_2_rc2.php b/phpBB/includes/db/migration/data/30x/3_0_2_rc2.php deleted file mode 100644 index 8917dfea77..0000000000 --- a/phpBB/includes/db/migration/data/30x/3_0_2_rc2.php +++ /dev/null @@ -1,80 +0,0 @@ -config['version'], '3.0.2-rc2', '>='); - } - - static public function depends_on() - { - return array('phpbb_db_migration_data_30x_3_0_2_rc1'); - } - - public function update_schema() - { - return array( - 'change_columns' => array( - $this->table_prefix . 'drafts' => array( - 'draft_subject' => array('STEXT_UNI', ''), - ), - $this->table_prefix . 'forums' => array( - 'forum_last_post_subject' => array('STEXT_UNI', ''), - ), - $this->table_prefix . 'posts' => array( - 'post_subject' => array('STEXT_UNI', '', 'true_sort'), - ), - $this->table_prefix . 'privmsgs' => array( - 'message_subject' => array('STEXT_UNI', ''), - ), - $this->table_prefix . 'topics' => array( - 'topic_title' => array('STEXT_UNI', '', 'true_sort'), - 'topic_last_post_subject' => array('STEXT_UNI', ''), - ), - ), - 'drop_keys' => array( - $this->table_prefix . 'sessions' => array( - 'session_forum_id', - ), - ), - 'add_index' => array( - $this->table_prefix . 'sessions' => array( - 'session_fid' => array('session_forum_id'), - ), - ), - ); - } - - public function revert_schema() - { - return array( - 'add_index' => array( - $this->table_prefix . 'sessions' => array( - 'session_forum_id' => array( - 'session_forum_id', - ), - ), - ), - 'drop_keys' => array( - $this->table_prefix . 'sessions' => array( - 'session_fid', - ), - ), - ); - } - - public function update_data() - { - return array( - array('config.update', array('version', '3.0.2-rc2')), - ); - } -} diff --git a/phpBB/includes/db/migration/data/30x/3_0_3.php b/phpBB/includes/db/migration/data/30x/3_0_3.php deleted file mode 100644 index 8984cf7b76..0000000000 --- a/phpBB/includes/db/migration/data/30x/3_0_3.php +++ /dev/null @@ -1,28 +0,0 @@ -config['version'], '3.0.3', '>='); - } - - static public function depends_on() - { - return array('phpbb_db_migration_data_30x_3_0_3_rc1'); - } - - public function update_data() - { - return array( - array('config.update', array('version', '3.0.3')), - ); - } -} diff --git a/phpBB/includes/db/migration/data/30x/3_0_3_rc1.php b/phpBB/includes/db/migration/data/30x/3_0_3_rc1.php deleted file mode 100644 index 4b102e1a2e..0000000000 --- a/phpBB/includes/db/migration/data/30x/3_0_3_rc1.php +++ /dev/null @@ -1,83 +0,0 @@ -config['version'], '3.0.3-rc1', '>='); - } - - static public function depends_on() - { - return array('phpbb_db_migration_data_30x_3_0_2'); - } - - public function update_schema() - { - return array( - 'add_columns' => array( - $this->table_prefix . 'styles_template' => array( - 'template_inherits_id' => array('UINT:4', 0), - 'template_inherit_path' => array('VCHAR', ''), - ), - $this->table_prefix . 'groups' => array( - 'group_max_recipients' => array('UINT', 0), - ), - ), - ); - } - - public function revert_schema() - { - return array( - 'drop_columns' => array( - $this->table_prefix . 'styles_template' => array( - 'template_inherits_id', - 'template_inherit_path', - ), - $this->table_prefix . 'groups' => array( - 'group_max_recipients', - ), - ), - ); - } - - public function update_data() - { - return array( - array('config.add', array('enable_queue_trigger', '0')), - array('config.add', array('queue_trigger_posts', '3')), - array('config.add', array('pm_max_recipients', '0')), - array('custom', array(array(&$this, 'set_group_default_max_recipients'))), - array('config.add', array('dbms_version', $this->db->sql_server_info(true))), - array('permission.add', array('u_masspm_group', true, 'u_masspm')), - array('custom', array(array(&$this, 'correct_acp_email_permissions'))), - - array('config.update', array('version', '3.0.3-rc1')), - ); - } - - public function correct_acp_email_permissions() - { - $sql = 'UPDATE ' . $this->table_prefix . 'modules - SET module_auth = \'acl_a_email && cfg_email_enable\' - WHERE module_class = \'acp\' - AND module_basename = \'email\''; - $this->sql_query($sql); - } - - public function set_group_default_max_recipients() - { - // Set maximum number of recipients for the registered users, bots, guests group - $sql = 'UPDATE ' . GROUPS_TABLE . ' SET group_max_recipients = 5 - WHERE ' . $this->db->sql_in_set('group_name', array('GUESTS', 'REGISTERED', 'REGISTERED_COPPA', 'BOTS')); - $this->sql_query($sql); - } -} diff --git a/phpBB/includes/db/migration/data/30x/3_0_4.php b/phpBB/includes/db/migration/data/30x/3_0_4.php deleted file mode 100644 index 9a0c132e78..0000000000 --- a/phpBB/includes/db/migration/data/30x/3_0_4.php +++ /dev/null @@ -1,49 +0,0 @@ -config['version'], '3.0.4', '>='); - } - - static public function depends_on() - { - return array('phpbb_db_migration_data_30x_3_0_4_rc1'); - } - - public function update_data() - { - return array( - array('custom', array(array(&$this, 'rename_log_delete_topic'))), - - array('config.update', array('version', '3.0.4')), - ); - } - - public function rename_log_delete_topic() - { - if ($this->db->sql_layer == 'oracle') - { - // log_operation is CLOB - but we can change this later - $sql = 'UPDATE ' . $this->table_prefix . "log - SET log_operation = 'LOG_DELETE_TOPIC' - WHERE log_operation LIKE 'LOG_TOPIC_DELETED'"; - $this->sql_query($sql); - } - else - { - $sql = 'UPDATE ' . $this->table_prefix . "log - SET log_operation = 'LOG_DELETE_TOPIC' - WHERE log_operation = 'LOG_TOPIC_DELETED'"; - $this->sql_query($sql); - } - } -} diff --git a/phpBB/includes/db/migration/data/30x/3_0_4_rc1.php b/phpBB/includes/db/migration/data/30x/3_0_4_rc1.php deleted file mode 100644 index 8ad75a557b..0000000000 --- a/phpBB/includes/db/migration/data/30x/3_0_4_rc1.php +++ /dev/null @@ -1,123 +0,0 @@ -config['version'], '3.0.4-rc1', '>='); - } - - static public function depends_on() - { - return array('phpbb_db_migration_data_30x_3_0_3'); - } - - public function update_schema() - { - return array( - 'add_columns' => array( - $this->table_prefix . 'profile_fields' => array( - 'field_show_profile' => array('BOOL', 0), - ), - ), - 'change_columns' => array( - $this->table_prefix . 'styles' => array( - 'style_id' => array('UINT', NULL, 'auto_increment'), - 'template_id' => array('UINT', 0), - 'theme_id' => array('UINT', 0), - 'imageset_id' => array('UINT', 0), - ), - $this->table_prefix . 'styles_imageset' => array( - 'imageset_id' => array('UINT', NULL, 'auto_increment'), - ), - $this->table_prefix . 'styles_imageset_data' => array( - 'image_id' => array('UINT', NULL, 'auto_increment'), - 'imageset_id' => array('UINT', 0), - ), - $this->table_prefix . 'styles_theme' => array( - 'theme_id' => array('UINT', NULL, 'auto_increment'), - ), - $this->table_prefix . 'styles_template' => array( - 'template_id' => array('UINT', NULL, 'auto_increment'), - ), - $this->table_prefix . 'styles_template_data' => array( - 'template_id' => array('UINT', 0), - ), - $this->table_prefix . 'forums' => array( - 'forum_style' => array('UINT', 0), - ), - $this->table_prefix . 'users' => array( - 'user_style' => array('UINT', 0), - ), - ), - ); - } - - public function revert_schema() - { - return array( - 'drop_columns' => array( - $this->table_prefix . 'profile_fields' => array( - 'field_show_profile', - ), - ), - ); - } - - public function update_data() - { - return array( - array('custom', array(array(&$this, 'update_custom_profile_fields'))), - - array('config.update', array('version', '3.0.4-rc1')), - ); - } - - public function update_custom_profile_fields() - { - // Update the Custom Profile Fields based on previous settings to the new format - $sql = 'SELECT field_id, field_required, field_show_on_reg, field_hide - FROM ' . PROFILE_FIELDS_TABLE; - $result = $this->db->sql_query($sql); - - while ($row = $this->db->sql_fetchrow($result)) - { - $sql_ary = array( - 'field_required' => 0, - 'field_show_on_reg' => 0, - 'field_hide' => 0, - 'field_show_profile'=> 0, - ); - - if ($row['field_required']) - { - $sql_ary['field_required'] = $sql_ary['field_show_on_reg'] = $sql_ary['field_show_profile'] = 1; - } - else if ($row['field_show_on_reg']) - { - $sql_ary['field_show_on_reg'] = $sql_ary['field_show_profile'] = 1; - } - else if ($row['field_hide']) - { - // Only administrators and moderators can see this CPF, if the view is enabled, they can see it, otherwise just admins in the acp_users module - $sql_ary['field_hide'] = 1; - } - else - { - // equivelant to "none", which is the "Display in user control panel" option - $sql_ary['field_show_profile'] = 1; - } - - $this->sql_query('UPDATE ' . $this->table_prefix . 'profile_fields SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . ' WHERE field_id = ' . $row['field_id'], $errored, $error_ary); - } - - $this->db->sql_freeresult($result); - } -} diff --git a/phpBB/includes/db/migration/data/30x/3_0_5.php b/phpBB/includes/db/migration/data/30x/3_0_5.php deleted file mode 100644 index 16d2dee457..0000000000 --- a/phpBB/includes/db/migration/data/30x/3_0_5.php +++ /dev/null @@ -1,28 +0,0 @@ -config['version'], '3.0.5', '>='); - } - - static public function depends_on() - { - return array('phpbb_db_migration_data_30x_3_0_5_rc1part2'); - } - - public function update_data() - { - return array( - array('config.update', array('version', '3.0.5')), - ); - } -} diff --git a/phpBB/includes/db/migration/data/30x/3_0_5_rc1.php b/phpBB/includes/db/migration/data/30x/3_0_5_rc1.php deleted file mode 100644 index ea17cc1e31..0000000000 --- a/phpBB/includes/db/migration/data/30x/3_0_5_rc1.php +++ /dev/null @@ -1,124 +0,0 @@ -config['version'], '3.0.5-rc1', '>='); - } - - static public function depends_on() - { - return array('phpbb_db_migration_data_30x_3_0_4'); - } - - public function update_schema() - { - return array( - 'change_columns' => array( - $this->table_prefix . 'forums' => array( - 'forum_style' => array('UINT', 0), - ), - ), - ); - } - - public function update_data() - { - $search_indexing_state = $this->config['search_indexing_state']; - - return array( - array('config.add', array('captcha_gd_wave', 0)), - array('config.add', array('captcha_gd_3d_noise', 1)), - array('config.add', array('captcha_gd_fonts', 1)), - array('config.add', array('confirm_refresh', 1)), - array('config.add', array('max_num_search_keywords', 10)), - array('config.remove', array('search_indexing_state')), - array('config.add', array('search_indexing_state', $search_indexing_state, true)), - array('custom', array(array(&$this, 'hash_old_passwords'))), - array('custom', array(array(&$this, 'update_ichiro_bot'))), - ); - } - - public function hash_old_passwords() - { - $sql = 'SELECT user_id, user_password - FROM ' . $this->table_prefix . 'users - WHERE user_pass_convert = 1'; - $result = $this->db->sql_query($sql); - - while ($row = $this->db->sql_fetchrow($result)) - { - if (strlen($row['user_password']) == 32) - { - $sql_ary = array( - 'user_password' => phpbb_hash($row['user_password']), - ); - - $this->sql_query('UPDATE ' . $this->table_prefix . 'users SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . ' WHERE user_id = ' . $row['user_id']); - } - } - $this->db->sql_freeresult($result); - } - - public function update_ichiro_bot() - { - // Adjust bot entry - $sql = 'UPDATE ' . $this->table_prefix . "bots - SET bot_agent = 'ichiro/' - WHERE bot_agent = 'ichiro/2'"; - $this->sql_query($sql); - } - - public function remove_duplicate_auth_options() - { - // Before we are able to add a unique key to auth_option, we need to remove duplicate entries - $sql = 'SELECT auth_option - FROM ' . $this->table_prefix . 'acl_options - GROUP BY auth_option - HAVING COUNT(*) >= 2'; - $result = $this->db->sql_query($sql); - - $auth_options = array(); - while ($row = $this->db->sql_fetchrow($result)) - { - $auth_options[] = $row['auth_option']; - } - $this->db->sql_freeresult($result); - - // Remove specific auth options - if (!empty($auth_options)) - { - foreach ($auth_options as $option) - { - // Select auth_option_ids... the largest id will be preserved - $sql = 'SELECT auth_option_id - FROM ' . ACL_OPTIONS_TABLE . " - WHERE auth_option = '" . $db->sql_escape($option) . "' - ORDER BY auth_option_id DESC"; - // sql_query_limit not possible here, due to bug in postgresql layer - $result = $this->db->sql_query($sql); - - // Skip first row, this is our original auth option we want to preserve - $row = $this->db->sql_fetchrow($result); - - while ($row = $this->db->sql_fetchrow($result)) - { - // Ok, remove this auth option... - $this->sql_query('DELETE FROM ' . ACL_OPTIONS_TABLE . ' WHERE auth_option_id = ' . $row['auth_option_id']); - $this->sql_query('DELETE FROM ' . ACL_ROLES_DATA_TABLE . ' WHERE auth_option_id = ' . $row['auth_option_id']); - $this->sql_query('DELETE FROM ' . ACL_GROUPS_TABLE . ' WHERE auth_option_id = ' . $row['auth_option_id']); - $this->sql_query('DELETE FROM ' . ACL_USERS_TABLE . ' WHERE auth_option_id = ' . $row['auth_option_id']); - } - $this->db->sql_freeresult($result); - } - } - } -} diff --git a/phpBB/includes/db/migration/data/30x/3_0_5_rc1part2.php b/phpBB/includes/db/migration/data/30x/3_0_5_rc1part2.php deleted file mode 100644 index 8538347b1a..0000000000 --- a/phpBB/includes/db/migration/data/30x/3_0_5_rc1part2.php +++ /dev/null @@ -1,42 +0,0 @@ -config['version'], '3.0.5-rc1', '>='); - } - - static public function depends_on() - { - return array('phpbb_db_migration_data_30x_3_0_5_rc1'); - } - - public function update_schema() - { - return array( - 'drop_keys' => array( - $this->table_prefix . 'acl_options' => array('auth_option'), - ), - 'add_unique_index' => array( - $this->table_prefix . 'acl_options' => array( - 'auth_option' => array('auth_option'), - ), - ), - ); - } - - public function update_data() - { - return array( - array('config.update', array('version', '3.0.5-rc1')), - ); - } -} diff --git a/phpBB/includes/db/migration/data/30x/3_0_6.php b/phpBB/includes/db/migration/data/30x/3_0_6.php deleted file mode 100644 index bb651dc7cd..0000000000 --- a/phpBB/includes/db/migration/data/30x/3_0_6.php +++ /dev/null @@ -1,28 +0,0 @@ -config['version'], '3.0.6', '>='); - } - - static public function depends_on() - { - return array('phpbb_db_migration_data_30x_3_0_6_rc4'); - } - - public function update_data() - { - return array( - array('config.update', array('version', '3.0.6')), - ); - } -} diff --git a/phpBB/includes/db/migration/data/30x/3_0_6_rc1.php b/phpBB/includes/db/migration/data/30x/3_0_6_rc1.php deleted file mode 100644 index 38c282ebf0..0000000000 --- a/phpBB/includes/db/migration/data/30x/3_0_6_rc1.php +++ /dev/null @@ -1,324 +0,0 @@ -config['version'], '3.0.6-rc1', '>='); - } - - static public function depends_on() - { - return array('phpbb_db_migration_data_30x_3_0_5'); - } - - public function update_schema() - { - return array( - 'add_columns' => array( - $this->table_prefix . 'confirm' => array( - 'attempts' => array('UINT', 0), - ), - $this->table_prefix . 'users' => array( - 'user_new' => array('BOOL', 1), - 'user_reminded' => array('TINT:4', 0), - 'user_reminded_time' => array('TIMESTAMP', 0), - ), - $this->table_prefix . 'groups' => array( - 'group_skip_auth' => array('BOOL', 0, 'after' => 'group_founder_manage'), - ), - $this->table_prefix . 'privmsgs' => array( - 'message_reported' => array('BOOL', 0), - ), - $this->table_prefix . 'reports' => array( - 'pm_id' => array('UINT', 0), - ), - $this->table_prefix . 'profile_fields' => array( - 'field_show_on_vt' => array('BOOL', 0), - ), - $this->table_prefix . 'forums' => array( - 'forum_options' => array('UINT:20', 0), - ), - ), - 'change_columns' => array( - $this->table_prefix . 'users' => array( - 'user_options' => array('UINT:11', 230271), - ), - ), - 'add_index' => array( - $this->table_prefix . 'reports' => array( - 'post_id' => array('post_id'), - 'pm_id' => array('pm_id'), - ), - $this->table_prefix . 'posts' => array( - 'post_username' => array('post_username:255'), - ), - ), - ); - } - - public function revert_schema() - { - return array( - 'drop_columns' => array( - $this->table_prefix . 'confirm' => array( - 'attempts', - ), - $this->table_prefix . 'users' => array( - 'user_new', - 'user_reminded', - 'user_reminded_time', - ), - $this->table_prefix . 'groups' => array( - 'group_skip_auth', - ), - $this->table_prefix . 'privmsgs' => array( - 'message_reported', - ), - $this->table_prefix . 'reports' => array( - 'pm_id', - ), - $this->table_prefix . 'profile_fields' => array( - 'field_show_on_vt', - ), - $this->table_prefix . 'forums' => array( - 'forum_options', - ), - ), - 'drop_keys' => array( - $this->table_prefix . 'reports' => array( - 'post_id', - 'pm_id', - ), - $this->table_prefix . 'posts' => array( - 'post_username', - ), - ), - ); - } - - public function update_data() - { - return array( - array('config.add', array('captcha_plugin', 'phpbb_captcha_nogd')), - array('if', array( - ($this->config['captcha_gd']), - array('config.update', array('captcha_plugin', 'phpbb_captcha_gd')), - )), - - array('config.add', array('feed_enable', 0)), - array('config.add', array('feed_limit', 10)), - array('config.add', array('feed_overall_forums', 1)), - array('config.add', array('feed_overall_forums_limit', 15)), - array('config.add', array('feed_overall_topics', 0)), - array('config.add', array('feed_overall_topics_limit', 15)), - array('config.add', array('feed_forum', 1)), - array('config.add', array('feed_topic', 1)), - array('config.add', array('feed_item_statistics', 1)), - - array('config.add', array('smilies_per_page', 50)), - array('config.add', array('allow_pm_report', 1)), - array('config.add', array('min_post_chars', 1)), - array('config.add', array('allow_quick_reply', 1)), - array('config.add', array('new_member_post_limit', 0)), - array('config.add', array('new_member_group_default', 0)), - array('config.add', array('delete_time', $this->config['edit_time'])), - - array('config.add', array('allow_avatar', 0)), - array('if', array( - ($this->config['allow_avatar_upload'] || $this->config['allow_avatar_local'] || $this->config['allow_avatar_remote']), - array('config.update', array('allow_avatar', 1)), - )), - array('config.add', array('allow_avatar_remote_upload', 0)), - array('if', array( - ($this->config['allow_avatar_remote'] && $this->config['allow_avatar_upload']), - array('config.update', array('allow_avatar_remote_upload', 1)), - )), - - array('module.add', array( - 'acp', - 'ACP_BOARD_CONFIGURATION', - array( - 'module_basename' => 'acp_board', - 'modes' => array('feed'), - ), - )), - array('module.add', array( - 'acp', - 'ACP_CAT_USERS', - array( - 'module_basename' => 'acp_users', - 'modes' => array('warnings'), - ), - )), - array('module.add', array( - 'acp', - 'ACP_SERVER_CONFIGURATION', - array( - 'module_basename' => 'acp_send_statistics', - 'modes' => array('send_statistics'), - ), - )), - array('module.add', array( - 'acp', - 'ACP_FORUM_BASED_PERMISSIONS', - array( - 'module_basename' => 'acp_permissions', - 'modes' => array('setting_forum_copy'), - ), - )), - array('module.add', array( - 'mcp', - 'MCP_REPORTS', - array( - 'module_basename' => 'mcp_pm_reports', - 'modes' => array('pm_reports','pm_reports_closed','pm_report_details'), - ), - )), - array('custom', array(array(&$this, 'add_newly_registered_group'))), - array('custom', array(array(&$this, 'set_user_options_default'))), - - array('config.update', array('version', '3.0.6-rc1')), - ); - } - - public function set_user_options_default() - { - // 229376 is the added value to enable all three signature options - $sql = 'UPDATE ' . USERS_TABLE . ' SET user_options = user_options + 229376'; - $this->sql_query($sql); - } - - public function add_newly_registered_group() - { - // Add newly_registered group... but check if it already exists (we always supported running the updater on any schema) - $sql = 'SELECT group_id - FROM ' . GROUPS_TABLE . " - WHERE group_name = 'NEWLY_REGISTERED'"; - $result = $this->db->sql_query($sql); - $group_id = (int) $this->db->sql_fetchfield('group_id'); - $this->db->sql_freeresult($result); - - if (!$group_id) - { - $sql = 'INSERT INTO ' . GROUPS_TABLE . " (group_name, group_type, group_founder_manage, group_colour, group_legend, group_avatar, group_desc, group_desc_uid, group_max_recipients) VALUES ('NEWLY_REGISTERED', 3, 0, '', 0, '', '', '', 5)"; - $this->sql_query($sql); - - $group_id = $this->db->sql_nextid(); - } - - // Insert new user role... at the end of the chain - $sql = 'SELECT role_id - FROM ' . ACL_ROLES_TABLE . " - WHERE role_name = 'ROLE_USER_NEW_MEMBER' - AND role_type = 'u_'"; - $result = $this->db->sql_query($sql); - $u_role = (int) $this->db->sql_fetchfield('role_id'); - $this->db->sql_freeresult($result); - - if (!$u_role) - { - $sql = 'SELECT MAX(role_order) as max_order_id - FROM ' . ACL_ROLES_TABLE . " - WHERE role_type = 'u_'"; - $result = $this->db->sql_query($sql); - $next_order_id = (int) $this->db->sql_fetchfield('max_order_id'); - $this->db->sql_freeresult($result); - - $next_order_id++; - - $sql = 'INSERT INTO ' . ACL_ROLES_TABLE . " (role_name, role_description, role_type, role_order) VALUES ('ROLE_USER_NEW_MEMBER', 'ROLE_DESCRIPTION_USER_NEW_MEMBER', 'u_', $next_order_id)"; - $this->sql_query($sql); - $u_role = $this->db->sql_nextid(); - - // Now add the correct data to the roles... - // The standard role says that new users are not able to send a PM, Mass PM, are not able to PM groups - $sql = 'INSERT INTO ' . ACL_ROLES_DATA_TABLE . " (role_id, auth_option_id, auth_setting) SELECT $u_role, auth_option_id, 0 FROM " . ACL_OPTIONS_TABLE . " WHERE auth_option LIKE 'u_%' AND auth_option IN ('u_sendpm', 'u_masspm', 'u_masspm_group')"; - $this->sql_query($sql); - - // Add user role to group - $sql = 'INSERT INTO ' . ACL_GROUPS_TABLE . " (group_id, forum_id, auth_option_id, auth_role_id, auth_setting) VALUES ($group_id, 0, 0, $u_role, 0)"; - $this->sql_query($sql); - } - - // Insert new forum role - $sql = 'SELECT role_id - FROM ' . ACL_ROLES_TABLE . " - WHERE role_name = 'ROLE_FORUM_NEW_MEMBER' - AND role_type = 'f_'"; - $result = $this->db->sql_query($sql); - $f_role = (int) $this->db->sql_fetchfield('role_id'); - $this->db->sql_freeresult($result); - - if (!$f_role) - { - $sql = 'SELECT MAX(role_order) as max_order_id - FROM ' . ACL_ROLES_TABLE . " - WHERE role_type = 'f_'"; - $result = $this->db->sql_query($sql); - $next_order_id = (int) $this->db->sql_fetchfield('max_order_id'); - $this->db->sql_freeresult($result); - - $next_order_id++; - - $sql = 'INSERT INTO ' . ACL_ROLES_TABLE . " (role_name, role_description, role_type, role_order) VALUES ('ROLE_FORUM_NEW_MEMBER', 'ROLE_DESCRIPTION_FORUM_NEW_MEMBER', 'f_', $next_order_id)"; - $this->sql_query($sql); - $f_role = $this->db->sql_nextid(); - - $sql = 'INSERT INTO ' . ACL_ROLES_DATA_TABLE . " (role_id, auth_option_id, auth_setting) SELECT $f_role, auth_option_id, 0 FROM " . ACL_OPTIONS_TABLE . " WHERE auth_option LIKE 'f_%' AND auth_option IN ('f_noapprove')"; - $this->sql_query($sql); - } - - // Set every members user_new column to 0 (old users) only if there is no one yet (this makes sure we do not execute this more than once) - $sql = 'SELECT 1 - FROM ' . USERS_TABLE . ' - WHERE user_new = 0'; - $result = $this->db->sql_query_limit($sql, 1); - $row = $this->db->sql_fetchrow($result); - $this->db->sql_freeresult($result); - - if (!$row) - { - $sql = 'UPDATE ' . USERS_TABLE . ' SET user_new = 0'; - $this->sql_query($sql); - } - - // To mimick the old "feature" we will assign the forum role to every forum, regardless of the setting (this makes sure there are no "this does not work!!!! YUO!!!" posts... - // Check if the role is already assigned... - $sql = 'SELECT forum_id - FROM ' . ACL_GROUPS_TABLE . ' - WHERE group_id = ' . $group_id . ' - AND auth_role_id = ' . $f_role; - $result = $this->db->sql_query($sql); - $is_options = (int) $this->db->sql_fetchfield('forum_id'); - $this->db->sql_freeresult($result); - - // Not assigned at all... :/ - if (!$is_options) - { - // Get postable forums - $sql = 'SELECT forum_id - FROM ' . FORUMS_TABLE . ' - WHERE forum_type != ' . FORUM_LINK; - $result = $this->db->sql_query($sql); - - while ($row = $this->db->sql_fetchrow($result)) - { - $this->sql_query('INSERT INTO ' . ACL_GROUPS_TABLE . ' (group_id, forum_id, auth_option_id, auth_role_id, auth_setting) VALUES (' . $group_id . ', ' . (int) $row['forum_id'] . ', 0, ' . $f_role . ', 0)'); - } - $this->db->sql_freeresult($result); - } - - // Clear permissions... - include_once($this->phpbb_root_path . 'includes/acp/auth.' . $this->php_ext); - $auth_admin = new auth_admin(); - $auth_admin->acl_clear_prefetch(); - } -} diff --git a/phpBB/includes/db/migration/data/30x/3_0_6_rc2.php b/phpBB/includes/db/migration/data/30x/3_0_6_rc2.php deleted file mode 100644 index a939dbd489..0000000000 --- a/phpBB/includes/db/migration/data/30x/3_0_6_rc2.php +++ /dev/null @@ -1,28 +0,0 @@ -config['version'], '3.0.6-rc2', '>='); - } - - static public function depends_on() - { - return array('phpbb_db_migration_data_30x_3_0_6_rc1'); - } - - public function update_data() - { - return array( - array('config.update', array('version', '3.0.6-rc2')), - ); - } -} diff --git a/phpBB/includes/db/migration/data/30x/3_0_6_rc3.php b/phpBB/includes/db/migration/data/30x/3_0_6_rc3.php deleted file mode 100644 index b3f09d8ab8..0000000000 --- a/phpBB/includes/db/migration/data/30x/3_0_6_rc3.php +++ /dev/null @@ -1,40 +0,0 @@ -config['version'], '3.0.6-rc3', '>='); - } - - static public function depends_on() - { - return array('phpbb_db_migration_data_30x_3_0_6_rc2'); - } - - public function update_data() - { - return array( - array('custom', array(array(&$this, 'update_cp_fields'))), - - array('config.update', array('version', '3.0.6-rc3')), - ); - } - - public function update_cp_fields() - { - // Update the Custom Profile Fields based on previous settings to the new format - $sql = 'UPDATE ' . PROFILE_FIELDS_TABLE . ' - SET field_show_on_vt = 1 - WHERE field_hide = 0 - AND (field_required = 1 OR field_show_on_reg = 1 OR field_show_profile = 1)'; - $this->sql_query($sql); - } -} diff --git a/phpBB/includes/db/migration/data/30x/3_0_6_rc4.php b/phpBB/includes/db/migration/data/30x/3_0_6_rc4.php deleted file mode 100644 index fc2923f99b..0000000000 --- a/phpBB/includes/db/migration/data/30x/3_0_6_rc4.php +++ /dev/null @@ -1,28 +0,0 @@ -config['version'], '3.0.6-rc4', '>='); - } - - static public function depends_on() - { - return array('phpbb_db_migration_data_30x_3_0_6_rc3'); - } - - public function update_data() - { - return array( - array('config.update', array('version', '3.0.6-rc4')), - ); - } -} diff --git a/phpBB/includes/db/migration/data/30x/3_0_7.php b/phpBB/includes/db/migration/data/30x/3_0_7.php deleted file mode 100644 index 9ff2e9e4ab..0000000000 --- a/phpBB/includes/db/migration/data/30x/3_0_7.php +++ /dev/null @@ -1,28 +0,0 @@ -config['version'], '3.0.7', '>='); - } - - static public function depends_on() - { - return array('phpbb_db_migration_data_30x_3_0_7_rc2'); - } - - public function update_data() - { - return array( - array('config.update', array('version', '3.0.7')), - ); - } -} diff --git a/phpBB/includes/db/migration/data/30x/3_0_7_pl1.php b/phpBB/includes/db/migration/data/30x/3_0_7_pl1.php deleted file mode 100644 index c9cc9d19ac..0000000000 --- a/phpBB/includes/db/migration/data/30x/3_0_7_pl1.php +++ /dev/null @@ -1,28 +0,0 @@ -config['version'], '3.0.7-pl1', '>='); - } - - static public function depends_on() - { - return array('phpbb_db_migration_data_30x_3_0_7'); - } - - public function update_data() - { - return array( - array('config.update', array('version', '3.0.7-pl1')), - ); - } -} diff --git a/phpBB/includes/db/migration/data/30x/3_0_7_rc1.php b/phpBB/includes/db/migration/data/30x/3_0_7_rc1.php deleted file mode 100644 index ffebf66f2d..0000000000 --- a/phpBB/includes/db/migration/data/30x/3_0_7_rc1.php +++ /dev/null @@ -1,76 +0,0 @@ -config['version'], '3.0.7-rc1', '>='); - } - - static public function depends_on() - { - return array('phpbb_db_migration_data_30x_3_0_6'); - } - - public function update_schema() - { - return array( - 'drop_keys' => array( - $this->table_prefix . 'log' => array( - 'log_time', - ), - ), - 'add_index' => array( - $this->table_prefix . 'topics_track' => array( - 'topic_id' => array('topic_id'), - ), - ), - ); - } - - public function revert_schema() - { - return array( - 'add_index' => array( - $this->table_prefix . 'log' => array( - 'log_time' => array('log_time'), - ), - ), - 'drop_keys' => array( - $this->table_prefix . 'topics_track' => array( - 'topic_id', - ), - ), - ); - } - - public function update_data() - { - return array( - array('config.add', array('feed_overall', 1)), - array('config.add', array('feed_http_auth', 0)), - array('config.add', array('feed_limit_post', $this->config['feed_limit'])), - array('config.add', array('feed_limit_topic', $this->config['feed_overall_topics_limit'])), - array('config.add', array('feed_topics_new', $this->config['feed_overall_topics'])), - array('config.add', array('feed_topics_active', $this->config['feed_overall_topics'])), - array('custom', array(array(&$this, 'delete_text_templates'))), - - array('config.update', array('version', '3.0.7-rc1')), - ); - } - - public function delete_text_templates() - { - // Delete all text-templates from the template_data - $sql = 'DELETE FROM ' . STYLES_TEMPLATE_DATA_TABLE . ' - WHERE template_filename ' . $this->db->sql_like_expression($this->db->any_char . '.txt'); - $this->sql_query($sql); - } -} diff --git a/phpBB/includes/db/migration/data/30x/3_0_7_rc2.php b/phpBB/includes/db/migration/data/30x/3_0_7_rc2.php deleted file mode 100644 index 55bc2bc679..0000000000 --- a/phpBB/includes/db/migration/data/30x/3_0_7_rc2.php +++ /dev/null @@ -1,73 +0,0 @@ -config['version'], '3.0.7-rc2', '>='); - } - - static public function depends_on() - { - return array('phpbb_db_migration_data_30x_3_0_7_rc1'); - } - - public function update_data() - { - return array( - array('custom', array(array(&$this, 'update_email_hash'))), - - array('config.update', array('version', '3.0.7-rc2')), - ); - } - - public function update_email_hash($start = 0) - { - $limit = 1000; - - $sql = 'SELECT user_id, user_email, user_email_hash - FROM ' . USERS_TABLE . ' - WHERE user_type <> ' . USER_IGNORE . " - AND user_email <> ''"; - $result = $this->db->sql_query_limit($sql, $limit, $start); - - $i = 0; - while ($row = $this->db->sql_fetchrow($result)) - { - $i++; - - // Snapshot of the phpbb_email_hash() function - // We cannot call it directly because the auto updater updates the DB first. :/ - $user_email_hash = sprintf('%u', crc32(strtolower($row['user_email']))) . strlen($row['user_email']); - - if ($user_email_hash != $row['user_email_hash']) - { - $sql_ary = array( - 'user_email_hash' => $user_email_hash, - ); - - $sql = 'UPDATE ' . USERS_TABLE . ' - SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . ' - WHERE user_id = ' . (int) $row['user_id']; - $this->sql_query($sql); - } - } - $this->db->sql_freeresult($result); - - if ($i < $limit) - { - // Completed - return; - } - - // Return the next start, will be sent to $start when this function is called again - return $start + $limit; - } -} diff --git a/phpBB/includes/db/migration/data/30x/3_0_8.php b/phpBB/includes/db/migration/data/30x/3_0_8.php deleted file mode 100644 index 8998ef9627..0000000000 --- a/phpBB/includes/db/migration/data/30x/3_0_8.php +++ /dev/null @@ -1,28 +0,0 @@ -config['version'], '3.0.8', '>='); - } - - static public function depends_on() - { - return array('phpbb_db_migration_data_30x_3_0_8_rc1'); - } - - public function update_data() - { - return array( - array('config.update', array('version', '3.0.8')), - ); - } -} diff --git a/phpBB/includes/db/migration/data/30x/3_0_8_rc1.php b/phpBB/includes/db/migration/data/30x/3_0_8_rc1.php deleted file mode 100644 index aeff35333e..0000000000 --- a/phpBB/includes/db/migration/data/30x/3_0_8_rc1.php +++ /dev/null @@ -1,221 +0,0 @@ -config['version'], '3.0.8-rc1', '>='); - } - - static public function depends_on() - { - return array('phpbb_db_migration_data_30x_3_0_7_pl1'); - } - - public function update_data() - { - return array( - array('custom', array(array(&$this, 'update_file_extension_group_names'))), - array('custom', array(array(&$this, 'update_module_auth'))), - array('custom', array(array(&$this, 'update_bots'))), - array('custom', array(array(&$this, 'delete_orphan_shadow_topics'))), - array('module.add', array( - 'acp', - 'ACP_MESSAGES', - array( - 'module_basename' => 'acp_board', - 'modes' => array('post'), - ), - )), - array('config.add', array('load_unreads_search', 1)), - array('config.update_if_equals', array(600, 'queue_interval', 60)), - array('config.update_if_equals', array(50, 'email_package_size', 20)), - - array('config.update', array('version', '3.0.8-rc1')), - ); - } - - public function update_file_extension_group_names() - { - // Update file extension group names to use language strings. - $sql = 'SELECT lang_dir - FROM ' . LANG_TABLE; - $result = $this->db->sql_query($sql); - - $extension_groups_updated = array(); - while ($lang_dir = $this->db->sql_fetchfield('lang_dir')) - { - $lang_dir = basename($lang_dir); - - // The language strings we need are either in language/.../acp/attachments.php - // in the update package if we're updating to 3.0.8-RC1 or later, - // or they are in language/.../install.php when we're updating from 3.0.7-PL1 or earlier. - // On an already updated board, they can also already be in language/.../acp/attachments.php - // in the board root. - $lang_files = array( - "{$this->phpbb_root_path}install/update/new/language/$lang_dir/acp/attachments.{$this->php_ext}", - "{$this->phpbb_root_path}language/$lang_dir/install.{$this->php_ext}", - "{$this->phpbb_root_path}language/$lang_dir/acp/attachments.{$this->php_ext}", - ); - - foreach ($lang_files as $lang_file) - { - if (!file_exists($lang_file)) - { - continue; - } - - $lang = array(); - include($lang_file); - - foreach($lang as $lang_key => $lang_val) - { - if (isset($extension_groups_updated[$lang_key]) || strpos($lang_key, 'EXT_GROUP_') !== 0) - { - continue; - } - - $sql_ary = array( - 'group_name' => substr($lang_key, 10), // Strip off 'EXT_GROUP_' - ); - - $sql = 'UPDATE ' . EXTENSION_GROUPS_TABLE . ' - SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . " - WHERE group_name = '" . $this->db->sql_escape($lang_val) . "'"; - $this->sql_query($sql); - - $extension_groups_updated[$lang_key] = true; - } - } - } - $this->db->sql_freeresult($result); - } - - public function update_module_auth() - { - $sql = 'UPDATE ' . MODULES_TABLE . ' - SET module_auth = \'cfg_allow_avatar && (cfg_allow_avatar_local || cfg_allow_avatar_remote || cfg_allow_avatar_upload || cfg_allow_avatar_remote_upload)\' - WHERE module_class = \'ucp\' - AND module_basename = \'profile\' - AND module_mode = \'avatar\''; - $this->sql_query($sql); - } - - public function update_bots() - { - $bot_name = 'Bing [Bot]'; - $bot_name_clean = utf8_clean_string($bot_name); - - $sql = 'SELECT user_id - FROM ' . USERS_TABLE . " - WHERE username_clean = '" . $this->db->sql_escape($bot_name_clean) . "'"; - $result = $this->db->sql_query($sql); - $bing_already_added = (bool) $this->db->sql_fetchfield('user_id'); - $this->db->sql_freeresult($result); - - if (!$bing_already_added) - { - $bot_agent = 'bingbot/'; - $bot_ip = ''; - $sql = 'SELECT group_id, group_colour - FROM ' . GROUPS_TABLE . " - WHERE group_name = 'BOTS'"; - $result = $this->db->sql_query($sql); - $group_row = $this->db->sql_fetchrow($result); - $this->db->sql_freeresult($result); - - if (!$group_row) - { - // default fallback, should never get here - $group_row['group_id'] = 6; - $group_row['group_colour'] = '9E8DA7'; - } - - if (!function_exists('user_add')) - { - include($this->phpbb_root_path . 'includes/functions_user.' . $this->php_ext); - } - - $user_row = array( - 'user_type' => USER_IGNORE, - 'group_id' => $group_row['group_id'], - 'username' => $bot_name, - 'user_regdate' => time(), - 'user_password' => '', - 'user_colour' => $group_row['group_colour'], - 'user_email' => '', - 'user_lang' => $this->config['default_lang'], - 'user_style' => $this->config['default_style'], - 'user_timezone' => 0, - 'user_dateformat' => $this->config['default_dateformat'], - 'user_allow_massemail' => 0, - ); - - $user_id = user_add($user_row); - - $sql = 'INSERT INTO ' . BOTS_TABLE . ' ' . $this->db->sql_build_array('INSERT', array( - 'bot_active' => 1, - 'bot_name' => (string) $bot_name, - 'user_id' => (int) $user_id, - 'bot_agent' => (string) $bot_agent, - 'bot_ip' => (string) $bot_ip, - )); - - $this->sql_query($sql); - } - } - - public function delete_orphan_shadow_topics() - { - // Delete shadow topics pointing to not existing topics - $batch_size = 500; - - // Set of affected forums we have to resync - $sync_forum_ids = array(); - - $sql_array = array( - 'SELECT' => 't1.topic_id, t1.forum_id', - 'FROM' => array( - TOPICS_TABLE => 't1', - ), - 'LEFT_JOIN' => array( - array( - 'FROM' => array(TOPICS_TABLE => 't2'), - 'ON' => 't1.topic_moved_id = t2.topic_id', - ), - ), - 'WHERE' => 't1.topic_moved_id <> 0 - AND t2.topic_id IS NULL', - ); - $sql = $this->db->sql_build_query('SELECT', $sql_array); - $result = $this->db->sql_query_limit($sql, $batch_size); - - $topic_ids = array(); - while ($row = $this->db->sql_fetchrow($result)) - { - $topic_ids[] = (int) $row['topic_id']; - - $sync_forum_ids[(int) $row['forum_id']] = (int) $row['forum_id']; - } - $this->db->sql_freeresult($result); - - if (!empty($topic_ids)) - { - $sql = 'DELETE FROM ' . TOPICS_TABLE . ' - WHERE ' . $this->db->sql_in_set('topic_id', $topic_ids); - $this->db->sql_query($sql); - - // Sync the forums we have deleted shadow topics from. - sync('forum', 'forum_id', $sync_forum_ids, true, true); - - return false; - } - } -} diff --git a/phpBB/includes/db/migration/data/30x/3_0_9.php b/phpBB/includes/db/migration/data/30x/3_0_9.php deleted file mode 100644 index d5269ea6f0..0000000000 --- a/phpBB/includes/db/migration/data/30x/3_0_9.php +++ /dev/null @@ -1,28 +0,0 @@ -config['version'], '3.0.9', '>='); - } - - static public function depends_on() - { - return array('phpbb_db_migration_data_30x_3_0_9_rc4'); - } - - public function update_data() - { - return array( - array('config.update', array('version', '3.0.9')), - ); - } -} diff --git a/phpBB/includes/db/migration/data/30x/3_0_9_rc1.php b/phpBB/includes/db/migration/data/30x/3_0_9_rc1.php deleted file mode 100644 index 4c345b429b..0000000000 --- a/phpBB/includes/db/migration/data/30x/3_0_9_rc1.php +++ /dev/null @@ -1,124 +0,0 @@ -config['version'], '3.0.9-rc1', '>='); - } - - static public function depends_on() - { - return array('phpbb_db_migration_data_30x_3_0_8'); - } - - public function update_schema() - { - return array( - 'add_tables' => array( - $this->table_prefix . 'login_attempts' => array( - 'COLUMNS' => array( - // this column was removed from the database updater - // after 3.0.9-RC3 was released. It might still exist - // in 3.0.9-RCX installations and has to be dropped as - // soon as the db_tools class is capable of properly - // removing a primary key. - // 'attempt_id' => array('UINT', NULL, 'auto_increment'), - 'attempt_ip' => array('VCHAR:40', ''), - 'attempt_browser' => array('VCHAR:150', ''), - 'attempt_forwarded_for' => array('VCHAR:255', ''), - 'attempt_time' => array('TIMESTAMP', 0), - 'user_id' => array('UINT', 0), - 'username' => array('VCHAR_UNI:255', 0), - 'username_clean' => array('VCHAR_CI', 0), - ), - //'PRIMARY_KEY' => 'attempt_id', - 'KEYS' => array( - 'att_ip' => array('INDEX', array('attempt_ip', 'attempt_time')), - 'att_for' => array('INDEX', array('attempt_forwarded_for', 'attempt_time')), - 'att_time' => array('INDEX', array('attempt_time')), - 'user_id' => array('INDEX', 'user_id'), - ), - ), - ), - 'change_columns' => array( - $this->table_prefix . 'bbcodes' => array( - 'bbcode_id' => array('USINT', 0), - ), - ), - ); - } - - public function revert_schema() - { - return array( - 'drop_tables' => array( - $this->table_prefix . 'login_attempts', - ), - ); - } - - public function update_data() - { - return array( - array('config.add', array('ip_login_limit_max', 50)), - array('config.add', array('ip_login_limit_time', 21600)), - array('config.add', array('ip_login_limit_use_forwarded', 0)), - array('custom', array(array(&$this, 'update_file_extension_group_names'))), - array('custom', array(array(&$this, 'fix_firebird_qa_captcha'))), - - array('config.update', array('version', '3.0.9-rc1')), - ); - } - - public function update_file_extension_group_names() - { - // Update file extension group names to use language strings, again. - $sql = 'SELECT group_id, group_name - FROM ' . EXTENSION_GROUPS_TABLE . ' - WHERE group_name ' . $this->db->sql_like_expression('EXT_GROUP_' . $this->db->any_char); - $result = $this->db->sql_query($sql); - - while ($row = $this->db->sql_fetchrow($result)) - { - $sql_ary = array( - 'group_name' => substr($row['group_name'], 10), // Strip off 'EXT_GROUP_' - ); - - $sql = 'UPDATE ' . EXTENSION_GROUPS_TABLE . ' - SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . ' - WHERE group_id = ' . $row['group_id']; - $this->sql_query($sql); - } - $this->db->sql_freeresult($result); - } - - public function fix_firebird_qa_captcha() - { - // Recover from potentially broken Q&A CAPTCHA table on firebird - // Q&A CAPTCHA was uninstallable, so it's safe to remove these - // without data loss - if ($this->db_tools->sql_layer == 'firebird') - { - $tables = array( - $this->table_prefix . 'captcha_questions', - $this->table_prefix . 'captcha_answers', - $this->table_prefix . 'qa_confirm', - ); - foreach ($tables as $table) - { - if ($this->db_tools->sql_table_exists($table)) - { - $this->db_tools->sql_table_drop($table); - } - } - } - } -} diff --git a/phpBB/includes/db/migration/data/30x/3_0_9_rc2.php b/phpBB/includes/db/migration/data/30x/3_0_9_rc2.php deleted file mode 100644 index c0e662aa45..0000000000 --- a/phpBB/includes/db/migration/data/30x/3_0_9_rc2.php +++ /dev/null @@ -1,28 +0,0 @@ -config['version'], '3.0.9-rc2', '>='); - } - - static public function depends_on() - { - return array('phpbb_db_migration_data_30x_3_0_9_rc1'); - } - - public function update_data() - { - return array( - array('config.update', array('version', '3.0.9-rc2')), - ); - } -} diff --git a/phpBB/includes/db/migration/data/30x/3_0_9_rc3.php b/phpBB/includes/db/migration/data/30x/3_0_9_rc3.php deleted file mode 100644 index d6d1f14b2e..0000000000 --- a/phpBB/includes/db/migration/data/30x/3_0_9_rc3.php +++ /dev/null @@ -1,28 +0,0 @@ -config['version'], '3.0.9-rc3', '>='); - } - - static public function depends_on() - { - return array('phpbb_db_migration_data_30x_3_0_9_rc2'); - } - - public function update_data() - { - return array( - array('config.update', array('version', '3.0.9-rc3')), - ); - } -} diff --git a/phpBB/includes/db/migration/data/30x/3_0_9_rc4.php b/phpBB/includes/db/migration/data/30x/3_0_9_rc4.php deleted file mode 100644 index e673249343..0000000000 --- a/phpBB/includes/db/migration/data/30x/3_0_9_rc4.php +++ /dev/null @@ -1,28 +0,0 @@ -config['version'], '3.0.9-rc4', '>='); - } - - static public function depends_on() - { - return array('phpbb_db_migration_data_30x_3_0_9_rc3'); - } - - public function update_data() - { - return array( - array('config.update', array('version', '3.0.9-rc4')), - ); - } -} diff --git a/phpBB/includes/db/migration/data/30x/local_url_bbcode.php b/phpBB/includes/db/migration/data/30x/local_url_bbcode.php deleted file mode 100644 index f324b8880d..0000000000 --- a/phpBB/includes/db/migration/data/30x/local_url_bbcode.php +++ /dev/null @@ -1,57 +0,0 @@ -db->sql_like_expression($this->db->any_char . 'LOCAL_URL' . $this->db->any_char); - $result = $this->db->sql_query($sql); - - while ($row = $this->db->sql_fetchrow($result)) - { - if (!class_exists('acp_bbcodes')) - { - global $phpEx; - phpbb_require_updated('includes/acp/acp_bbcodes.' . $phpEx); - } - $bbcode_match = $row['bbcode_match']; - $bbcode_tpl = $row['bbcode_tpl']; - - $acp_bbcodes = new acp_bbcodes(); - $sql_ary = $acp_bbcodes->build_regexp($bbcode_match, $bbcode_tpl); - - $sql = 'UPDATE ' . BBCODES_TABLE . ' - SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . ' - WHERE bbcode_id = ' . (int) $row['bbcode_id']; - $this->sql_query($sql); - } - $this->db->sql_freeresult($result); - } -} diff --git a/phpBB/includes/db/migration/data/310/avatars.php b/phpBB/includes/db/migration/data/310/avatars.php deleted file mode 100644 index 79547337f7..0000000000 --- a/phpBB/includes/db/migration/data/310/avatars.php +++ /dev/null @@ -1,67 +0,0 @@ -config['allow_avatar_gravatar']); - } - - static public function depends_on() - { - return array('phpbb_db_migration_data_30x_3_0_11'); - } - - public function update_schema() - { - return array( - 'change_columns' => array( - $this->table_prefix . 'users' => array( - 'user_avatar_type' => array('VCHAR:255', ''), - ), - $this->table_prefix . 'groups' => array( - 'group_avatar_type' => array('VCHAR:255', ''), - ), - ), - ); - } - - public function revert_schema() - { - return array( - 'change_columns' => array( - $this->table_prefix . 'users' => array( - 'user_avatar_type' => array('TINT:2', ''), - ), - $this->table_prefix . 'groups' => array( - 'group_avatar_type' => array('TINT:2', ''), - ), - ), - ); - } - - public function update_data() - { - return array( - array('config.add', array('allow_avatar_gravatar', 0)), - array('custom', array(array($this, 'update_module_auth'))), - ); - } - - public function update_module_auth() - { - $sql = 'UPDATE ' . $this->table_prefix . "modules - SET module_auth = 'cfg_allow_avatar' - WHERE module_class = 'ucp' - AND module_basename = 'ucp_profile' - AND module_mode = 'avatar'"; - $this->db->sql_query($sql); - } -} diff --git a/phpBB/includes/db/migration/data/310/boardindex.php b/phpBB/includes/db/migration/data/310/boardindex.php deleted file mode 100644 index 965e32c15c..0000000000 --- a/phpBB/includes/db/migration/data/310/boardindex.php +++ /dev/null @@ -1,23 +0,0 @@ -config['board_index_text']); - } - - public function update_data() - { - return array( - array('config.add', array('board_index_text', '')), - ); - } -} diff --git a/phpBB/includes/db/migration/data/310/config_db_text.php b/phpBB/includes/db/migration/data/310/config_db_text.php deleted file mode 100644 index 89f211adda..0000000000 --- a/phpBB/includes/db/migration/data/310/config_db_text.php +++ /dev/null @@ -1,45 +0,0 @@ -db_tools->sql_table_exists($this->table_prefix . 'config_text'); - } - - static public function depends_on() - { - return array('phpbb_db_migration_data_30x_3_0_11'); - } - - public function update_schema() - { - return array( - 'add_tables' => array( - $this->table_prefix . 'config_text' => array( - 'COLUMNS' => array( - 'config_name' => array('VCHAR', ''), - 'config_value' => array('MTEXT', ''), - ), - 'PRIMARY_KEY' => 'config_name', - ), - ), - ); - } - - public function revert_schema() - { - return array( - 'drop_tables' => array( - $this->table_prefix . 'config_text', - ), - ); - } -} diff --git a/phpBB/includes/db/migration/data/310/dev.php b/phpBB/includes/db/migration/data/310/dev.php deleted file mode 100644 index 0fc2950987..0000000000 --- a/phpBB/includes/db/migration/data/310/dev.php +++ /dev/null @@ -1,408 +0,0 @@ -config['version'], '3.1.0-dev', '>='); - } - - static public function depends_on() - { - return array( - 'phpbb_db_migration_data_310_extensions', - 'phpbb_db_migration_data_310_style_update_p2', - 'phpbb_db_migration_data_310_timezone_p2', - 'phpbb_db_migration_data_310_reported_posts_display', - ); - } - - public function update_schema() - { - return array( - 'add_columns' => array( - $this->table_prefix . 'groups' => array( - 'group_teampage' => array('UINT', 0, 'after' => 'group_legend'), - ), - $this->table_prefix . 'profile_fields' => array( - 'field_show_on_pm' => array('BOOL', 0), - ), - $this->table_prefix . 'styles' => array( - 'style_path' => array('VCHAR:100', ''), - 'bbcode_bitfield' => array('VCHAR:255', 'kNg='), - 'style_parent_id' => array('UINT:4', 0), - 'style_parent_tree' => array('TEXT', ''), - ), - $this->table_prefix . 'reports' => array( - 'reported_post_text' => array('MTEXT_UNI', ''), - 'reported_post_uid' => array('VCHAR:8', ''), - 'reported_post_bitfield' => array('VCHAR:255', ''), - ), - ), - 'change_columns' => array( - $this->table_prefix . 'groups' => array( - 'group_legend' => array('UINT', 0), - ), - ), - ); - } - - public function revert_schema() - { - return array( - 'drop_columns' => array( - $this->table_prefix . 'groups' => array( - 'group_teampage', - ), - $this->table_prefix . 'profile_fields' => array( - 'field_show_on_pm', - ), - $this->table_prefix . 'styles' => array( - 'style_path', - 'bbcode_bitfield', - 'style_parent_id', - 'style_parent_tree', - ), - $this->table_prefix . 'reports' => array( - 'reported_post_text', - 'reported_post_uid', - 'reported_post_bitfield', - ), - ), - ); - } - - public function update_data() - { - return array( - array('if', array( - (strpos('phpbb_search_', $this->config['search_type']) !== 0), - array('config.update', array('search_type', 'phpbb_search_' . $this->config['search_type'])), - )), - - array('config.add', array('fulltext_postgres_ts_name', 'simple')), - array('config.add', array('fulltext_postgres_min_word_len', 4)), - array('config.add', array('fulltext_postgres_max_word_len', 254)), - array('config.add', array('fulltext_sphinx_stopwords', 0)), - array('config.add', array('fulltext_sphinx_indexer_mem_limit', 512)), - - array('config.add', array('load_jquery_cdn', 0)), - array('config.add', array('load_jquery_url', '//ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js')), - - array('config.add', array('use_system_cron', 0)), - - array('config.add', array('legend_sort_groupname', 0)), - array('config.add', array('teampage_forums', 1)), - array('config.add', array('teampage_memberships', 1)), - - array('config.add', array('load_cpf_pm', 0)), - - array('config.add', array('display_last_subject', 1)), - - array('config.add', array('assets_version', 1)), - - array('config.add', array('site_home_url', '')), - array('config.add', array('site_home_text', '')), - - array('permission.add', array('u_chgprofileinfo', true, 'u_sig')), - - array('module.add', array( - 'acp', - 'ACP_GROUPS', - array( - 'module_basename' => 'acp_groups', - 'modes' => array('position'), - ), - )), - array('module.add', array( - 'acp', - 'ACP_ATTACHMENTS', - array( - 'module_basename' => 'acp_attachments', - 'modes' => array('manage'), - ), - )), - array('module.add', array( - 'acp', - 'ACP_STYLE_MANAGEMENT', - array( - 'module_basename' => 'acp_styles', - 'modes' => array('install', 'cache'), - ), - )), - array('module.add', array( - 'ucp', - 'UCP_PROFILE', - array( - 'module_basename' => 'ucp_profile', - 'modes' => array('autologin_keys'), - ), - )), - // Module will be renamed later - array('module.add', array( - 'acp', - 'ACP_CAT_STYLES', - 'ACP_LANGUAGE' - )), - - array('module.remove', array( - 'acp', - false, - 'ACP_TEMPLATES', - )), - array('module.remove', array( - 'acp', - false, - 'ACP_THEMES', - )), - array('module.remove', array( - 'acp', - false, - 'ACP_IMAGESETS', - )), - - array('custom', array(array($this, 'rename_module_basenames'))), - array('custom', array(array($this, 'rename_styles_module'))), - array('custom', array(array($this, 'add_group_teampage'))), - array('custom', array(array($this, 'update_group_legend'))), - array('custom', array(array($this, 'localise_global_announcements'))), - array('custom', array(array($this, 'update_ucp_pm_basename'))), - array('custom', array(array($this, 'update_ucp_profile_auth'))), - array('custom', array(array($this, 'move_customise_modules'))), - - array('config.update', array('version', '3.1.0-dev')), - ); - } - - public function move_customise_modules() - { - // Move language management to new location in the Customise tab - // First get language module id - $sql = 'SELECT module_id FROM ' . MODULES_TABLE . " - WHERE module_basename = 'acp_language'"; - $result = $this->db->sql_query($sql); - $language_module_id = $this->db->sql_fetchfield('module_id'); - $this->db->sql_freeresult($result); - // Next get language management module id of the one just created - $sql = 'SELECT module_id FROM ' . MODULES_TABLE . " - WHERE module_langname = 'ACP_LANGUAGE'"; - $result = $this->db->sql_query($sql); - $language_management_module_id = $this->db->sql_fetchfield('module_id'); - $this->db->sql_freeresult($result); - - if (!class_exists('acp_modules')) - { - include($this->phpbb_root_path . 'includes/acp/acp_modules.' . $this->php_ext); - } - // acp_modules calls adm_back_link, which is undefined at this point - if (!function_exists('adm_back_link')) - { - include($this->phpbb_root_path . 'includes/functions_acp.' . $this->php_ext); - } - $module_manager = new acp_modules(); - $module_manager->module_class = 'acp'; - $module_manager->move_module($language_module_id, $language_management_module_id); - } - - public function update_ucp_pm_basename() - { - $sql = 'SELECT module_id, module_basename - FROM ' . MODULES_TABLE . " - WHERE module_basename <> 'ucp_pm' AND - module_langname='UCP_PM'"; - $result = $this->db->sql_query_limit($sql, 1); - - if ($row = $this->db->sql_fetchrow($result)) - { - // This update is still not applied. Applying it - - $sql = 'UPDATE ' . MODULES_TABLE . " - SET module_basename = 'ucp_pm' - WHERE module_id = " . (int) $row['module_id']; - - $this->sql_query($sql); - } - $this->db->sql_freeresult($result); - } - - public function update_ucp_profile_auth() - { - // Update the auth setting for the module - $sql = 'UPDATE ' . MODULES_TABLE . " - SET module_auth = 'acl_u_chgprofileinfo' - WHERE module_class = 'ucp' - AND module_basename = 'ucp_profile' - AND module_mode = 'profile_info'"; - $this->sql_query($sql); - } - - public function rename_styles_module() - { - // Rename styles module to Customise - $sql = 'UPDATE ' . MODULES_TABLE . " - SET module_langname = 'ACP_CAT_CUSTOMISE' - WHERE module_langname = 'ACP_CAT_STYLES'"; - $this->sql_query($sql); - } - - public function rename_module_basenames() - { - // rename all module basenames to full classname - $sql = 'SELECT module_id, module_basename, module_class - FROM ' . MODULES_TABLE; - $result = $this->db->sql_query($sql); - - while ($row = $this->db->sql_fetchrow($result)) - { - $module_id = (int) $row['module_id']; - unset($row['module_id']); - - if (!empty($row['module_basename']) && !empty($row['module_class'])) - { - // all the class names start with class name or with phpbb_ for auto loading - if (strpos($row['module_basename'], $row['module_class'] . '_') !== 0 && - strpos($row['module_basename'], 'phpbb_') !== 0) - { - $row['module_basename'] = $row['module_class'] . '_' . $row['module_basename']; - - $sql_update = $this->db->sql_build_array('UPDATE', $row); - - $sql = 'UPDATE ' . MODULES_TABLE . ' - SET ' . $sql_update . ' - WHERE module_id = ' . $module_id; - $this->sql_query($sql); - } - } - } - - $this->db->sql_freeresult($result); - } - - public function add_group_teampage() - { - $sql = 'UPDATE ' . GROUPS_TABLE . ' - SET group_teampage = 1 - WHERE group_type = ' . GROUP_SPECIAL . " - AND group_name = 'ADMINISTRATORS'"; - $this->sql_query($sql); - - $sql = 'UPDATE ' . GROUPS_TABLE . ' - SET group_teampage = 2 - WHERE group_type = ' . GROUP_SPECIAL . " - AND group_name = 'GLOBAL_MODERATORS'"; - $this->sql_query($sql); - } - - public function update_group_legend() - { - $sql = 'SELECT group_id - FROM ' . GROUPS_TABLE . ' - WHERE group_legend = 1 - ORDER BY group_name ASC'; - $result = $this->db->sql_query($sql); - - $next_legend = 1; - while ($row = $this->db->sql_fetchrow($result)) - { - $sql = 'UPDATE ' . GROUPS_TABLE . ' - SET group_legend = ' . $next_legend . ' - WHERE group_id = ' . (int) $row['group_id']; - $this->sql_query($sql); - - $next_legend++; - } - $this->db->sql_freeresult($result); - } - - public function localise_global_announcements() - { - // Localise Global Announcements - $sql = 'SELECT topic_id, topic_approved, (topic_replies + 1) AS topic_posts, topic_last_post_id, topic_last_post_subject, topic_last_post_time, topic_last_poster_id, topic_last_poster_name, topic_last_poster_colour - FROM ' . TOPICS_TABLE . ' - WHERE forum_id = 0 - AND topic_type = ' . POST_GLOBAL; - $result = $this->db->sql_query($sql); - - $global_announcements = $update_lastpost_data = array(); - $update_lastpost_data['forum_last_post_time'] = 0; - $update_forum_data = array( - 'forum_posts' => 0, - 'forum_topics' => 0, - 'forum_topics_real' => 0, - ); - - while ($row = $this->db->sql_fetchrow($result)) - { - $global_announcements[] = (int) $row['topic_id']; - - $update_forum_data['forum_posts'] += (int) $row['topic_posts']; - $update_forum_data['forum_topics_real']++; - if ($row['topic_approved']) - { - $update_forum_data['forum_topics']++; - } - - if ($update_lastpost_data['forum_last_post_time'] < $row['topic_last_post_time']) - { - $update_lastpost_data = array( - 'forum_last_post_id' => (int) $row['topic_last_post_id'], - 'forum_last_post_subject' => $row['topic_last_post_subject'], - 'forum_last_post_time' => (int) $row['topic_last_post_time'], - 'forum_last_poster_id' => (int) $row['topic_last_poster_id'], - 'forum_last_poster_name' => $row['topic_last_poster_name'], - 'forum_last_poster_colour' => $row['topic_last_poster_colour'], - ); - } - } - $this->db->sql_freeresult($result); - - if (!empty($global_announcements)) - { - // Update the post/topic-count for the forum and the last-post if needed - $sql = 'SELECT forum_id - FROM ' . FORUMS_TABLE . ' - WHERE forum_type = ' . FORUM_POST; - $result = $this->db->sql_query_limit($sql, 1); - $ga_forum_id = $this->db->sql_fetchfield('forum_id'); - $this->db->sql_freeresult($result); - - $sql = 'SELECT forum_last_post_time - FROM ' . FORUMS_TABLE . ' - WHERE forum_id = ' . $ga_forum_id; - $result = $this->db->sql_query($sql); - $lastpost = (int) $this->db->sql_fetchfield('forum_last_post_time'); - $this->db->sql_freeresult($result); - - $sql_update = 'forum_posts = forum_posts + ' . $update_forum_data['forum_posts'] . ', '; - $sql_update .= 'forum_topics_real = forum_topics_real + ' . $update_forum_data['forum_topics_real'] . ', '; - $sql_update .= 'forum_topics = forum_topics + ' . $update_forum_data['forum_topics']; - if ($lastpost < $update_lastpost_data['forum_last_post_time']) - { - $sql_update .= ', ' . $this->db->sql_build_array('UPDATE', $update_lastpost_data); - } - - $sql = 'UPDATE ' . FORUMS_TABLE . ' - SET ' . $sql_update . ' - WHERE forum_id = ' . $ga_forum_id; - $this->sql_query($sql); - - // Update some forum_ids - $table_ary = array(TOPICS_TABLE, POSTS_TABLE, LOG_TABLE, DRAFTS_TABLE, TOPICS_TRACK_TABLE); - foreach ($table_ary as $table) - { - $sql = "UPDATE $table - SET forum_id = $ga_forum_id - WHERE " . $this->db->sql_in_set('topic_id', $global_announcements); - $this->sql_query($sql); - } - unset($table_ary); - } - } -} diff --git a/phpBB/includes/db/migration/data/310/extensions.php b/phpBB/includes/db/migration/data/310/extensions.php deleted file mode 100644 index 6a9caa1cfc..0000000000 --- a/phpBB/includes/db/migration/data/310/extensions.php +++ /dev/null @@ -1,69 +0,0 @@ -db_tools->sql_table_exists($this->table_prefix . 'ext'); - } - - static public function depends_on() - { - return array('phpbb_db_migration_data_30x_3_0_11'); - } - - public function update_schema() - { - return array( - 'add_tables' => array( - $this->table_prefix . 'ext' => array( - 'COLUMNS' => array( - 'ext_name' => array('VCHAR', ''), - 'ext_active' => array('BOOL', 0), - 'ext_state' => array('TEXT', ''), - ), - 'KEYS' => array( - 'ext_name' => array('UNIQUE', 'ext_name'), - ), - ), - ), - ); - } - - public function revert_schema() - { - return array( - 'drop_tables' => array( - $this->table_prefix . 'ext', - ), - ); - } - - public function update_data() - { - return array( - // Module will be renamed later - array('module.add', array( - 'acp', - 'ACP_CAT_STYLES', - 'ACP_EXTENSION_MANAGEMENT' - )), - array('module.add', array( - 'acp', - 'ACP_EXTENSION_MANAGEMENT', - array( - 'module_basename' => 'acp_extensions', - 'modes' => array('main'), - ), - )), - array('permission.add', array('a_extensions', true, 'a_styles')), - ); - } -} diff --git a/phpBB/includes/db/migration/data/310/forgot_password.php b/phpBB/includes/db/migration/data/310/forgot_password.php deleted file mode 100644 index a553e51f35..0000000000 --- a/phpBB/includes/db/migration/data/310/forgot_password.php +++ /dev/null @@ -1,28 +0,0 @@ -config['allow_password_reset']); - } - - static public function depends_on() - { - return array('phpbb_db_migration_data_30x_3_0_11'); - } - - public function update_data() - { - return array( - array('config.add', array('allow_password_reset', 1)), - ); - } -} diff --git a/phpBB/includes/db/migration/data/310/jquery_update.php b/phpBB/includes/db/migration/data/310/jquery_update.php deleted file mode 100644 index dc49f74fcb..0000000000 --- a/phpBB/includes/db/migration/data/310/jquery_update.php +++ /dev/null @@ -1,31 +0,0 @@ -config['load_jquery_url'] !== '//ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js'; - } - - static public function depends_on() - { - return array( - 'phpbb_db_migration_data_310_dev', - ); - } - - public function update_data() - { - return array( - array('config.update', array('load_jquery_url', '//ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js')), - ); - } - -} diff --git a/phpBB/includes/db/migration/data/310/notification_options_reconvert.php b/phpBB/includes/db/migration/data/310/notification_options_reconvert.php deleted file mode 100644 index d994d7ec5f..0000000000 --- a/phpBB/includes/db/migration/data/310/notification_options_reconvert.php +++ /dev/null @@ -1,118 +0,0 @@ -table_prefix . 'user_notifications'; - $insert_buffer = new phpbb_db_sql_insert_buffer($this->db, $insert_table); - - $this->perform_conversion($insert_buffer, $insert_table); - } - - /** - * Perform the conversion (separate for testability) - * - * @param phpbb_db_sql_insert_buffer $insert_buffer - * @param string $insert_table - */ - public function perform_conversion(phpbb_db_sql_insert_buffer $insert_buffer, $insert_table) - { - $sql = 'DELETE FROM ' . $insert_table; - $this->db->sql_query($sql); - - $sql = 'SELECT user_id, user_notify_type, user_notify_pm - FROM ' . USERS_TABLE; - $result = $this->db->sql_query($sql); - - while ($row = $this->db->sql_fetchrow($result)) - { - $notification_methods = array(); - - // In-board notification - $notification_methods[] = ''; - - if ($row['user_notify_type'] == NOTIFY_EMAIL || $row['user_notify_type'] == NOTIFY_BOTH) - { - $notification_methods[] = 'email'; - } - - if ($row['user_notify_type'] == NOTIFY_IM || $row['user_notify_type'] == NOTIFY_BOTH) - { - $notification_methods[] = 'jabber'; - } - - // Notifications for posts - foreach (array('post', 'topic') as $item_type) - { - $this->add_method_rows( - $insert_buffer, - $item_type, - 0, - $row['user_id'], - $notification_methods - ); - } - - if ($row['user_notify_pm']) - { - // Notifications for private messages - // User either gets all methods or no method - $this->add_method_rows( - $insert_buffer, - 'pm', - 0, - $row['user_id'], - $notification_methods - ); - } - } - $this->db->sql_freeresult($result); - - $insert_buffer->flush(); - } - - /** - * Insert method rows to DB - * - * @param phpbb_db_sql_insert_buffer $insert_buffer - * @param string $item_type - * @param int $item_id - * @param int $user_id - * @param string $methods - */ - protected function add_method_rows(phpbb_db_sql_insert_buffer $insert_buffer, $item_type, $item_id, $user_id, array $methods) - { - $row_base = array( - 'item_type' => $item_type, - 'item_id' => (int) $item_id, - 'user_id' => (int) $user_id, - 'notify' => 1 - ); - - foreach ($methods as $method) - { - $row_base['method'] = $method; - $insert_buffer->insert($row_base); - } - } -} diff --git a/phpBB/includes/db/migration/data/310/notifications.php b/phpBB/includes/db/migration/data/310/notifications.php deleted file mode 100644 index 17c939d95a..0000000000 --- a/phpBB/includes/db/migration/data/310/notifications.php +++ /dev/null @@ -1,96 +0,0 @@ -db_tools->sql_table_exists($this->table_prefix . 'notifications'); - } - - static public function depends_on() - { - return array('phpbb_db_migration_data_310_dev'); - } - - public function update_schema() - { - return array( - 'add_tables' => array( - $this->table_prefix . 'notification_types' => array( - 'COLUMNS' => array( - 'notification_type' => array('VCHAR:255', ''), - 'notification_type_enabled' => array('BOOL', 1), - ), - 'PRIMARY_KEY' => array('notification_type', 'notification_type_enabled'), - ), - $this->table_prefix . 'notifications' => array( - 'COLUMNS' => array( - 'notification_id' => array('UINT', NULL, 'auto_increment'), - 'item_type' => array('VCHAR:255', ''), - 'item_id' => array('UINT', 0), - 'item_parent_id' => array('UINT', 0), - 'user_id' => array('UINT', 0), - 'notification_read' => array('BOOL', 0), - 'notification_time' => array('TIMESTAMP', 1), - 'notification_data' => array('TEXT_UNI', ''), - ), - 'PRIMARY_KEY' => 'notification_id', - 'KEYS' => array( - 'item_ident' => array('INDEX', array('item_type', 'item_id')), - 'user' => array('INDEX', array('user_id', 'notification_read')), - ), - ), - $this->table_prefix . 'user_notifications' => array( - 'COLUMNS' => array( - 'item_type' => array('VCHAR:255', ''), - 'item_id' => array('UINT', 0), - 'user_id' => array('UINT', 0), - 'method' => array('VCHAR:255', ''), - 'notify' => array('BOOL', 1), - ), - ), - ), - ); - } - - public function revert_schema() - { - return array( - 'drop_tables' => array( - $this->table_prefix . 'notification_types', - $this->table_prefix . 'notifications', - $this->table_prefix . 'user_notifications', - ), - ); - } - - public function update_data() - { - return array( - array('module.add', array( - 'ucp', - 'UCP_MAIN', - array( - 'module_basename' => 'ucp_notifications', - 'modes' => array('notification_list'), - ), - )), - array('module.add', array( - 'ucp', - 'UCP_PREFS', - array( - 'module_basename' => 'ucp_notifications', - 'modes' => array('notification_options'), - ), - )), - array('config.add', array('load_notifications', 1)), - ); - } -} diff --git a/phpBB/includes/db/migration/data/310/notifications_schema_fix.php b/phpBB/includes/db/migration/data/310/notifications_schema_fix.php deleted file mode 100644 index 27e63e10d0..0000000000 --- a/phpBB/includes/db/migration/data/310/notifications_schema_fix.php +++ /dev/null @@ -1,92 +0,0 @@ - array( - $this->table_prefix . 'notification_types', - $this->table_prefix . 'notifications', - ), - 'add_tables' => array( - $this->table_prefix . 'notification_types' => array( - 'COLUMNS' => array( - 'notification_type_id' => array('USINT', NULL, 'auto_increment'), - 'notification_type_name' => array('VCHAR:255', ''), - 'notification_type_enabled' => array('BOOL', 1), - ), - 'PRIMARY_KEY' => array('notification_type_id'), - 'KEYS' => array( - 'type' => array('UNIQUE', array('notification_type_name')), - ), - ), - $this->table_prefix . 'notifications' => array( - 'COLUMNS' => array( - 'notification_id' => array('UINT:10', NULL, 'auto_increment'), - 'notification_type_id' => array('USINT', 0), - 'item_id' => array('UINT', 0), - 'item_parent_id' => array('UINT', 0), - 'user_id' => array('UINT', 0), - 'notification_read' => array('BOOL', 0), - 'notification_time' => array('TIMESTAMP', 1), - 'notification_data' => array('TEXT_UNI', ''), - ), - 'PRIMARY_KEY' => 'notification_id', - 'KEYS' => array( - 'item_ident' => array('INDEX', array('notification_type_id', 'item_id')), - 'user' => array('INDEX', array('user_id', 'notification_read')), - ), - ), - ), - ); - } - - public function revert_schema() - { - return array( - 'drop_tables' => array( - $this->table_prefix . 'notification_types', - $this->table_prefix . 'notifications', - ), - 'add_tables' => array( - $this->table_prefix . 'notification_types' => array( - 'COLUMNS' => array( - 'notification_type' => array('VCHAR:255', ''), - 'notification_type_enabled' => array('BOOL', 1), - ), - 'PRIMARY_KEY' => array('notification_type', 'notification_type_enabled'), - ), - $this->table_prefix . 'notifications' => array( - 'COLUMNS' => array( - 'notification_id' => array('UINT', NULL, 'auto_increment'), - 'item_type' => array('VCHAR:255', ''), - 'item_id' => array('UINT', 0), - 'item_parent_id' => array('UINT', 0), - 'user_id' => array('UINT', 0), - 'notification_read' => array('BOOL', 0), - 'notification_time' => array('TIMESTAMP', 1), - 'notification_data' => array('TEXT_UNI', ''), - ), - 'PRIMARY_KEY' => 'notification_id', - 'KEYS' => array( - 'item_ident' => array('INDEX', array('item_type', 'item_id')), - 'user' => array('INDEX', array('user_id', 'notification_read')), - ), - ), - ), - ); - } -} diff --git a/phpBB/includes/db/migration/data/310/reported_posts_display.php b/phpBB/includes/db/migration/data/310/reported_posts_display.php deleted file mode 100644 index 80a0a0e43f..0000000000 --- a/phpBB/includes/db/migration/data/310/reported_posts_display.php +++ /dev/null @@ -1,47 +0,0 @@ -db_tools->sql_column_exists($this->table_prefix . 'reports', 'reported_post_enable_bbcode'); - } - - static public function depends_on() - { - return array('phpbb_db_migration_data_30x_3_0_11'); - } - - public function update_schema() - { - return array( - 'add_columns' => array( - $this->table_prefix . 'reports' => array( - 'reported_post_enable_bbcode' => array('BOOL', 1), - 'reported_post_enable_smilies' => array('BOOL', 1), - 'reported_post_enable_magic_url' => array('BOOL', 1), - ), - ), - ); - } - - public function revert_schema() - { - return array( - 'drop_columns' => array( - $this->table_prefix . 'reports' => array( - 'reported_post_enable_bbcode', - 'reported_post_enable_smilies', - 'reported_post_enable_magic_url', - ), - ), - ); - } -} diff --git a/phpBB/includes/db/migration/data/310/signature_module_auth.php b/phpBB/includes/db/migration/data/310/signature_module_auth.php deleted file mode 100644 index e4fbb27bcb..0000000000 --- a/phpBB/includes/db/migration/data/310/signature_module_auth.php +++ /dev/null @@ -1,51 +0,0 @@ -db->sql_query($sql); - $module_auth = $this->db_sql_fetchfield('module_auth'); - $this->db->sql_freeresult($result); - - return $module_auth === 'acl_u_sig' || $module_auth === false; - } - - static public function depends_on() - { - return array('phpbb_db_migration_data_31x_dev'); - } - - public function update_data() - { - return array( - array('custom', array( - array($this, 'update_signature_module_auth'), - ), - ), - ); - } - - public function update_signature_module_auth() - { - $sql = 'UPDATE ' . MODULES_TABLE . " - SET module_auth = 'acl_u_sig' - WHERE module_class = 'ucp' - AND module_basename = 'ucp_profile' - AND module_mode = 'signature' - AND module_auth = ''"; - $this->db->sql_query($sql); - } -} diff --git a/phpBB/includes/db/migration/data/310/softdelete_p1.php b/phpBB/includes/db/migration/data/310/softdelete_p1.php deleted file mode 100644 index 84f8eebd4a..0000000000 --- a/phpBB/includes/db/migration/data/310/softdelete_p1.php +++ /dev/null @@ -1,171 +0,0 @@ -db_tools->sql_column_exists($this->table_prefix . 'posts', 'post_visibility'); - } - - static public function depends_on() - { - return array('phpbb_db_migration_data_310_dev'); - } - - public function update_schema() - { - return array( - 'add_columns' => array( - $this->table_prefix . 'forums' => array( - 'forum_posts_approved' => array('UINT', 0), - 'forum_posts_unapproved' => array('UINT', 0), - 'forum_posts_softdeleted' => array('UINT', 0), - 'forum_topics_approved' => array('UINT', 0), - 'forum_topics_unapproved' => array('UINT', 0), - 'forum_topics_softdeleted' => array('UINT', 0), - ), - $this->table_prefix . 'posts' => array( - 'post_visibility' => array('TINT:3', 0), - 'post_delete_time' => array('TIMESTAMP', 0), - 'post_delete_reason' => array('STEXT_UNI', ''), - 'post_delete_user' => array('UINT', 0), - ), - $this->table_prefix . 'topics' => array( - 'topic_visibility' => array('TINT:3', 0), - 'topic_delete_time' => array('TIMESTAMP', 0), - 'topic_delete_reason' => array('STEXT_UNI', ''), - 'topic_delete_user' => array('UINT', 0), - 'topic_posts_approved' => array('UINT', 0), - 'topic_posts_unapproved' => array('UINT', 0), - 'topic_posts_softdeleted' => array('UINT', 0), - ), - ), - 'add_index' => array( - $this->table_prefix . 'posts' => array( - 'post_visibility' => array('post_visibility'), - ), - $this->table_prefix . 'topics' => array( - 'topic_visibility' => array('topic_visibility'), - 'forum_vis_last' => array('forum_id', 'topic_visibility', 'topic_last_post_id'), - ), - ), - ); - } - - public function revert_schema() - { - return array( - 'drop_columns' => array( - $this->table_prefix . 'forums' => array( - 'forum_posts_approved', - 'forum_posts_unapproved', - 'forum_posts_softdeleted', - 'forum_topics_approved', - 'forum_topics_unapproved', - 'forum_topics_softdeleted', - ), - $this->table_prefix . 'posts' => array( - 'post_visibility', - 'post_delete_time', - 'post_delete_reason', - 'post_delete_user', - ), - $this->table_prefix . 'topics' => array( - 'topic_visibility', - 'topic_delete_time', - 'topic_delete_reason', - 'topic_delete_user', - 'topic_posts_approved', - 'topic_posts_unapproved', - 'topic_posts_softdeleted', - ), - ), - 'drop_keys' => array( - $this->table_prefix . 'posts' => array('post_visibility'), - $this->table_prefix . 'topics' => array('topic_visibility', 'forum_vis_last'), - ), - ); - } - - public function update_data() - { - return array( - array('custom', array(array($this, 'update_post_visibility'))), - array('custom', array(array($this, 'update_topic_visibility'))), - array('custom', array(array($this, 'update_topic_forum_counts'))), - - array('permission.add', array('f_softdelete', false)), - array('permission.add', array('m_softdelete', false)), - ); - } - - public function update_post_visibility() - { - $sql = 'UPDATE ' . $this->table_prefix . 'posts - SET post_visibility = post_approved'; - $this->sql_query($sql); - } - - public function update_topic_visibility() - { - $sql = 'UPDATE ' . $this->table_prefix . 'topics - SET topic_visibility = topic_approved'; - $this->sql_query($sql); - } - - public function update_topic_forum_counts() - { - $sql = 'UPDATE ' . $this->table_prefix . 'topics - SET topic_posts_approved = topic_replies + 1, - topic_posts_unapproved = topic_replies_real - topic_replies - WHERE topic_visibility = ' . ITEM_APPROVED; - $this->sql_query($sql); - - $sql = 'UPDATE ' . $this->table_prefix . 'topics - SET topic_posts_approved = 0, - topic_posts_unapproved = (topic_replies_real - topic_replies) + 1 - WHERE topic_visibility = ' . ITEM_UNAPPROVED; - $this->sql_query($sql); - - $sql = 'SELECT forum_id, topic_visibility, COUNT(topic_id) AS sum_topics, SUM(topic_posts_approved) AS sum_posts_approved, SUM(topic_posts_unapproved) AS sum_posts_unapproved - FROM ' . $this->table_prefix . 'topics - GROUP BY forum_id, topic_visibility'; - $result = $this->db->sql_query($sql); - - $update_forums = array(); - while ($row = $this->db->sql_fetchrow($result)) - { - $forum_id = (int) $row['forum_id']; - if (!isset($update_forums[$forum_id])) - { - $update_forums[$forum_id] = array( - 'forum_posts_approved' => 0, - 'forum_posts_unapproved' => 0, - 'forum_topics_approved' => 0, - 'forum_topics_unapproved' => 0, - ); - } - - $update_forums[$forum_id]['forum_posts_approved'] += (int) $row['sum_posts_approved']; - $update_forums[$forum_id]['forum_posts_unapproved'] += (int) $row['sum_posts_unapproved']; - - $update_forums[$forum_id][(($row['topic_visibility'] == ITEM_APPROVED) ? 'forum_topics_approved' : 'forum_topics_unapproved')] += (int) $row['sum_topics']; - } - $this->db->sql_freeresult($result); - - foreach ($update_forums as $forum_id => $forum_data) - { - $sql = 'UPDATE ' . FORUMS_TABLE . ' - SET ' . $this->db->sql_build_array('UPDATE', $forum_data) . ' - WHERE forum_id = ' . $forum_id; - $this->sql_query($sql); - } - } -} diff --git a/phpBB/includes/db/migration/data/310/softdelete_p2.php b/phpBB/includes/db/migration/data/310/softdelete_p2.php deleted file mode 100644 index 7320a2c2bf..0000000000 --- a/phpBB/includes/db/migration/data/310/softdelete_p2.php +++ /dev/null @@ -1,68 +0,0 @@ -db_tools->sql_column_exists($this->table_prefix . 'posts', 'post_approved'); - } - - static public function depends_on() - { - return array( - 'phpbb_db_migration_data_310_dev', - 'phpbb_db_migration_data_310_softdelete_p1', - ); - } - - public function update_schema() - { - return array( - 'drop_columns' => array( - $this->table_prefix . 'forums' => array('forum_posts', 'forum_topics', 'forum_topics_real'), - $this->table_prefix . 'posts' => array('post_approved'), - $this->table_prefix . 'topics' => array('topic_approved', 'topic_replies', 'topic_replies_real'), - ), - 'drop_keys' => array( - $this->table_prefix . 'posts' => array('post_approved'), - $this->table_prefix . 'topics' => array('forum_appr_last'), - ), - ); - } - - public function revert_schema() - { - return array( - 'add_columns' => array( - $this->table_prefix . 'forums' => array( - 'forum_posts' => array('UINT', 0), - 'forum_topics' => array('UINT', 0), - 'forum_topics_real' => array('UINT', 0), - ), - $this->table_prefix . 'posts' => array( - 'post_approved' => array('BOOL', 1), - ), - $this->table_prefix . 'topics' => array( - 'topic_approved' => array('BOOL', 1), - 'topic_replies' => array('UINT', 0), - 'topic_replies_real' => array('UINT', 0), - ), - ), - 'add_index' => array( - $this->table_prefix . 'posts' => array( - 'post_approved' => array('post_approved'), - ), - $this->table_prefix . 'topics' => array( - 'forum_appr_last' => array('forum_id', 'topic_approved', 'topic_last_post_id'), - ), - ), - ); - } -} diff --git a/phpBB/includes/db/migration/data/310/style_update_p1.php b/phpBB/includes/db/migration/data/310/style_update_p1.php deleted file mode 100644 index d43537559d..0000000000 --- a/phpBB/includes/db/migration/data/310/style_update_p1.php +++ /dev/null @@ -1,185 +0,0 @@ -db_tools->sql_table_exists($this->table_prefix . 'styles_imageset'); - } - - static public function depends_on() - { - return array('phpbb_db_migration_data_30x_3_0_11'); - } - - public function update_schema() - { - return array( - 'add_columns' => array( - $this->table_prefix . 'styles' => array( - 'style_path' => array('VCHAR:100', ''), - 'bbcode_bitfield' => array('VCHAR:255', 'kNg='), - 'style_parent_id' => array('UINT', 0), - 'style_parent_tree' => array('TEXT', ''), - ), - ), - ); - } - - public function revert_schema() - { - return array( - 'drop_columns' => array( - $this->table_prefix . 'styles' => array( - 'style_path', - 'bbcode_bitfield', - 'style_parent_id', - 'style_parent_tree', - ), - ), - ); - } - - public function update_data() - { - return array( - array('custom', array(array($this, 'styles_update'))), - ); - } - - public function styles_update() - { - // Get list of valid 3.1 styles - $available_styles = array('prosilver'); - - $iterator = new DirectoryIterator($this->phpbb_root_path . 'styles'); - $skip_dirs = array('.', '..', 'prosilver'); - foreach ($iterator as $fileinfo) - { - if ($fileinfo->isDir() && !in_array($fileinfo->getFilename(), $skip_dirs) && file_exists($fileinfo->getPathname() . '/style.cfg')) - { - $style_cfg = parse_cfg_file($fileinfo->getPathname() . '/style.cfg'); - if (isset($style_cfg['phpbb_version']) && version_compare($style_cfg['phpbb_version'], '3.1.0-dev', '>=')) - { - // 3.1 style - $available_styles[] = $fileinfo->getFilename(); - } - } - } - - // Get all installed styles - if ($this->db_tools->sql_table_exists($this->table_prefix . 'styles_imageset')) - { - $sql = 'SELECT s.style_id, t.template_path, t.template_id, t.bbcode_bitfield, t.template_inherits_id, t.template_inherit_path, c.theme_path, c.theme_id, i.imageset_path - FROM ' . STYLES_TABLE . ' s, ' . $this->table_prefix . 'styles_template t, ' . $this->table_prefix . 'styles_theme c, ' . $this->table_prefix . "styles_imageset i - WHERE t.template_id = s.template_id - AND c.theme_id = s.theme_id - AND i.imageset_id = s.imageset_id"; - } - else - { - $sql = 'SELECT s.style_id, t.template_path, t.template_id, t.bbcode_bitfield, t.template_inherits_id, t.template_inherit_path, c.theme_path, c.theme_id - FROM ' . STYLES_TABLE . ' s, ' . $this->table_prefix . 'styles_template t, ' . $this->table_prefix . "stles_theme c - WHERE t.template_id = s.template_id - AND c.theme_id = s.theme_id"; - } - $result = $this->db->sql_query($sql); - - $styles = array(); - while ($row = $this->db->sql_fetchrow($result)) - { - $styles[] = $row; - } - $this->db->sql_freeresult($result); - - // Decide which styles to keep, all others will be deleted - $valid_styles = array(); - foreach ($styles as $style_row) - { - if ( - // Delete styles with parent style (not supported yet) - $style_row['template_inherits_id'] == 0 && - // Check if components match - $style_row['template_path'] == $style_row['theme_path'] && (!isset($style_row['imageset_path']) || $style_row['template_path'] == $style_row['imageset_path']) && - // Check if components are valid - in_array($style_row['template_path'], $available_styles) - ) - { - // Valid style. Keep it - $sql_ary = array( - 'style_path' => $style_row['template_path'], - 'bbcode_bitfield' => $style_row['bbcode_bitfield'], - 'style_parent_id' => 0, - 'style_parent_tree' => '', - ); - $this->sql_query('UPDATE ' . STYLES_TABLE . ' - SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . ' - WHERE style_id = ' . $style_row['style_id']); - $valid_styles[] = (int) $style_row['style_id']; - } - } - - // Remove old entries from styles table - if (!sizeof($valid_styles)) - { - // No valid styles: remove everything and add prosilver - $this->sql_query('DELETE FROM ' . STYLES_TABLE, $errored, $error_ary); - - $sql_ary = array( - 'style_name' => 'prosilver', - 'style_copyright' => '© phpBB Group', - 'style_active' => 1, - 'style_path' => 'prosilver', - 'bbcode_bitfield' => 'lNg=', - 'style_parent_id' => 0, - 'style_parent_tree' => '', - - // Will be removed in the next step - 'imageset_id' => 0, - 'template_id' => 0, - 'theme_id' => 0, - ); - - $sql = 'INSERT INTO ' . STYLES_TABLE . ' ' . $this->db->sql_build_array('INSERT', $sql_ary); - $this->sql_query($sql); - - $sql = 'SELECT style_id - FROM ' . $table . " - WHERE style_name = 'prosilver'"; - $result = $this->sql_query($sql); - $default_style = $this->db->sql_fetchfield($result); - $this->db->sql_freeresult($result); - - set_config('default_style', $default_style); - - $sql = 'UPDATE ' . USERS_TABLE . ' SET user_style = 0'; - $this->sql_query($sql); - } - else - { - // There are valid styles in styles table. Remove styles that are outdated - $this->sql_query('DELETE FROM ' . STYLES_TABLE . ' - WHERE ' . $this->db->sql_in_set('style_id', $valid_styles, true)); - - // Change default style - if (!in_array($this->config['default_style'], $valid_styles)) - { - $this->sql_query('UPDATE ' . CONFIG_TABLE . " - SET config_value = '" . $valid_styles[0] . "' - WHERE config_name = 'default_style'"); - } - - // Reset styles for users - $this->sql_query('UPDATE ' . USERS_TABLE . ' - SET user_style = 0 - WHERE ' . $this->db->sql_in_set('user_style', $valid_styles, true)); - } - } -} diff --git a/phpBB/includes/db/migration/data/310/style_update_p2.php b/phpBB/includes/db/migration/data/310/style_update_p2.php deleted file mode 100644 index 7b10518a66..0000000000 --- a/phpBB/includes/db/migration/data/310/style_update_p2.php +++ /dev/null @@ -1,129 +0,0 @@ -db_tools->sql_table_exists($this->table_prefix . 'styles_imageset'); - } - - static public function depends_on() - { - return array('phpbb_db_migration_data_310_style_update_p1'); - } - - public function update_schema() - { - return array( - 'drop_columns' => array( - $this->table_prefix . 'styles' => array( - 'imageset_id', - 'template_id', - 'theme_id', - ), - ), - - 'drop_tables' => array( - $this->table_prefix . 'styles_imageset', - $this->table_prefix . 'styles_imageset_data', - $this->table_prefix . 'styles_template', - $this->table_prefix . 'styles_template_data', - $this->table_prefix . 'styles_theme', - ), - ); - } - - public function revert_schema() - { - return array( - 'add_columns' => array( - $this->table_prefix . 'styles' => array( - 'imageset_id' => array('UINT', 0), - 'template_id' => array('UINT', 0), - 'theme_id' => array('UINT', 0), - ), - ), - - 'add_tables' => array( - $this->table_prefix . 'styles_imageset' => array( - 'COLUMNS' => array( - 'imageset_id' => array('UINT', NULL, 'auto_increment'), - 'imageset_name' => array('VCHAR_UNI:255', ''), - 'imageset_copyright' => array('VCHAR_UNI', ''), - 'imageset_path' => array('VCHAR:100', ''), - ), - 'PRIMARY_KEY' => 'imageset_id', - 'KEYS' => array( - 'imgset_nm' => array('UNIQUE', 'imageset_name'), - ), - ), - $this->table_prefix . 'styles_imageset_data' => array( - 'COLUMNS' => array( - 'image_id' => array('UINT', NULL, 'auto_increment'), - 'image_name' => array('VCHAR:200', ''), - 'image_filename' => array('VCHAR:200', ''), - 'image_lang' => array('VCHAR:30', ''), - 'image_height' => array('USINT', 0), - 'image_width' => array('USINT', 0), - 'imageset_id' => array('UINT', 0), - ), - 'PRIMARY_KEY' => 'image_id', - 'KEYS' => array( - 'i_d' => array('INDEX', 'imageset_id'), - ), - ), - $this->table_prefix . 'styles_template' => array( - 'COLUMNS' => array( - 'template_id' => array('UINT', NULL, 'auto_increment'), - 'template_name' => array('VCHAR_UNI:255', ''), - 'template_copyright' => array('VCHAR_UNI', ''), - 'template_path' => array('VCHAR:100', ''), - 'bbcode_bitfield' => array('VCHAR:255', 'kNg='), - 'template_storedb' => array('BOOL', 0), - 'template_inherits_id' => array('UINT:4', 0), - 'template_inherit_path' => array('VCHAR', ''), - ), - 'PRIMARY_KEY' => 'template_id', - 'KEYS' => array( - 'tmplte_nm' => array('UNIQUE', 'template_name'), - ), - ), - $this->table_prefix . 'styles_template_data' => array( - 'COLUMNS' => array( - 'template_id' => array('UINT', 0), - 'template_filename' => array('VCHAR:100', ''), - 'template_included' => array('TEXT', ''), - 'template_mtime' => array('TIMESTAMP', 0), - 'template_data' => array('MTEXT_UNI', ''), - ), - 'KEYS' => array( - 'tid' => array('INDEX', 'template_id'), - 'tfn' => array('INDEX', 'template_filename'), - ), - ), - $this->table_prefix . 'styles_theme' => array( - 'COLUMNS' => array( - 'theme_id' => array('UINT', NULL, 'auto_increment'), - 'theme_name' => array('VCHAR_UNI:255', ''), - 'theme_copyright' => array('VCHAR_UNI', ''), - 'theme_path' => array('VCHAR:100', ''), - 'theme_storedb' => array('BOOL', 0), - 'theme_mtime' => array('TIMESTAMP', 0), - 'theme_data' => array('MTEXT_UNI', ''), - ), - 'PRIMARY_KEY' => 'theme_id', - 'KEYS' => array( - 'theme_name' => array('UNIQUE', 'theme_name'), - ), - ), - ), - ); - } -} diff --git a/phpBB/includes/db/migration/data/310/teampage.php b/phpBB/includes/db/migration/data/310/teampage.php deleted file mode 100644 index 4e77da17b7..0000000000 --- a/phpBB/includes/db/migration/data/310/teampage.php +++ /dev/null @@ -1,104 +0,0 @@ -db_tools->sql_table_exists($this->table_prefix . 'teampage'); - } - - static public function depends_on() - { - return array('phpbb_db_migration_data_310_dev'); - } - - public function update_schema() - { - return array( - 'add_tables' => array( - $this->table_prefix . 'teampage' => array( - 'COLUMNS' => array( - 'teampage_id' => array('UINT', NULL, 'auto_increment'), - 'group_id' => array('UINT', 0), - 'teampage_name' => array('VCHAR_UNI:255', ''), - 'teampage_position' => array('UINT', 0), - 'teampage_parent' => array('UINT', 0), - ), - 'PRIMARY_KEY' => 'teampage_id', - ), - ), - 'drop_columns' => array( - $this->table_prefix . 'groups' => array( - 'group_teampage', - ), - ), - ); - } - - public function revert_schema() - { - return array( - 'drop_tables' => array( - $this->table_prefix . 'teampage', - ), - 'add_columns' => array( - $this->table_prefix . 'groups' => array( - 'group_teampage' => array('UINT', 0, 'after' => 'group_legend'), - ), - ), - ); - } - - public function update_data() - { - return array( - array('custom', array(array($this, 'add_groups_teampage'))), - ); - } - - public function add_groups_teampage() - { - $sql = 'SELECT teampage_id - FROM ' . TEAMPAGE_TABLE; - $result = $this->db->sql_query_limit($sql, 1); - $added_groups_teampage = (bool) $this->db->sql_fetchfield('teampage_id'); - $this->db->sql_freeresult($result); - - if (!$added_groups_teampage) - { - $sql = 'SELECT * - FROM ' . GROUPS_TABLE . ' - WHERE group_type = ' . GROUP_SPECIAL . " - AND (group_name = 'ADMINISTRATORS' - OR group_name = 'GLOBAL_MODERATORS') - ORDER BY group_name ASC"; - $result = $this->db->sql_query($sql); - - $teampage_entries = array(); - while ($row = $this->db->sql_fetchrow($result)) - { - $teampage_entries[] = array( - 'group_id' => (int) $row['group_id'], - 'teampage_name' => '', - 'teampage_position' => sizeof($teampage_entries) + 1, - 'teampage_parent' => 0, - ); - } - $this->db->sql_freeresult($result); - - if (sizeof($teampage_entries)) - { - $this->db->sql_multi_insert(TEAMPAGE_TABLE, $teampage_entries); - } - unset($teampage_entries); - } - - } -} diff --git a/phpBB/includes/db/migration/data/310/timezone.php b/phpBB/includes/db/migration/data/310/timezone.php deleted file mode 100644 index 6e50cbe45f..0000000000 --- a/phpBB/includes/db/migration/data/310/timezone.php +++ /dev/null @@ -1,163 +0,0 @@ -db_tools->sql_column_exists($this->table_prefix . 'users', 'user_dst'); - } - - static public function depends_on() - { - return array('phpbb_db_migration_data_30x_3_0_11'); - } - - public function update_schema() - { - return array( - 'change_columns' => array( - $this->table_prefix . 'users' => array( - 'user_timezone' => array('VCHAR:100', ''), - ), - ), - ); - } - - public function update_data() - { - return array( - array('custom', array(array($this, 'update_timezones'))), - ); - } - - public function update_timezones() - { - // Update user timezones - $sql = 'SELECT user_dst, user_timezone - FROM ' . $this->table_prefix . 'users - GROUP BY user_timezone, user_dst'; - $result = $this->db->sql_query($sql); - - while ($row = $this->db->sql_fetchrow($result)) - { - $sql = 'UPDATE ' . $this->table_prefix . "users - SET user_timezone = '" . $this->db->sql_escape($this->convert_phpbb30_timezone($row['user_timezone'], $row['user_dst'])) . "' - WHERE user_timezone = '" . $this->db->sql_escape($row['user_timezone']) . "' - AND user_dst = " . (int) $row['user_dst']; - $this->sql_query($sql); - } - $this->db->sql_freeresult($result); - - // Update board default timezone - $sql = 'UPDATE ' . $this->table_prefix . "config - SET config_value = '" . $this->convert_phpbb30_timezone($this->config['board_timezone'], $this->config['board_dst']) . "' - WHERE config_name = 'board_timezone'"; - $this->sql_query($sql); - } - - /** - * Determine the new timezone for a given phpBB 3.0 timezone and - * "Daylight Saving Time" option - * - * @param $timezone float Users timezone in 3.0 - * @param $dst int Users daylight saving time - * @return string Users new php Timezone which is used since 3.1 - */ - public function convert_phpbb30_timezone($timezone, $dst) - { - $offset = $timezone + $dst; - - switch ($timezone) - { - case '-12': - return 'Etc/GMT+' . abs($offset); //'[UTC - 12] Baker Island Time' - case '-11': - return 'Etc/GMT+' . abs($offset); //'[UTC - 11] Niue Time, Samoa Standard Time' - case '-10': - return 'Etc/GMT+' . abs($offset); //'[UTC - 10] Hawaii-Aleutian Standard Time, Cook Island Time' - case '-9.5': - return 'Pacific/Marquesas'; //'[UTC - 9:30] Marquesas Islands Time' - case '-9': - return 'Etc/GMT+' . abs($offset); //'[UTC - 9] Alaska Standard Time, Gambier Island Time' - case '-8': - return 'Etc/GMT+' . abs($offset); //'[UTC - 8] Pacific Standard Time' - case '-7': - return 'Etc/GMT+' . abs($offset); //'[UTC - 7] Mountain Standard Time' - case '-6': - return 'Etc/GMT+' . abs($offset); //'[UTC - 6] Central Standard Time' - case '-5': - return 'Etc/GMT+' . abs($offset); //'[UTC - 5] Eastern Standard Time' - case '-4.5': - return 'America/Caracas'; //'[UTC - 4:30] Venezuelan Standard Time' - case '-4': - return 'Etc/GMT+' . abs($offset); //'[UTC - 4] Atlantic Standard Time' - case '-3.5': - return 'America/St_Johns'; //'[UTC - 3:30] Newfoundland Standard Time' - case '-3': - return 'Etc/GMT+' . abs($offset); //'[UTC - 3] Amazon Standard Time, Central Greenland Time' - case '-2': - return 'Etc/GMT+' . abs($offset); //'[UTC - 2] Fernando de Noronha Time, South Georgia & the South Sandwich Islands Time' - case '-1': - return 'Etc/GMT+' . abs($offset); //'[UTC - 1] Azores Standard Time, Cape Verde Time, Eastern Greenland Time' - case '0': - return (!$dst) ? 'UTC' : 'Etc/GMT-1'; //'[UTC] Western European Time, Greenwich Mean Time' - case '1': - return 'Etc/GMT-' . $offset; //'[UTC + 1] Central European Time, West African Time' - case '2': - return 'Etc/GMT-' . $offset; //'[UTC + 2] Eastern European Time, Central African Time' - case '3': - return 'Etc/GMT-' . $offset; //'[UTC + 3] Moscow Standard Time, Eastern African Time' - case '3.5': - return 'Asia/Tehran'; //'[UTC + 3:30] Iran Standard Time' - case '4': - return 'Etc/GMT-' . $offset; //'[UTC + 4] Gulf Standard Time, Samara Standard Time' - case '4.5': - return 'Asia/Kabul'; //'[UTC + 4:30] Afghanistan Time' - case '5': - return 'Etc/GMT-' . $offset; //'[UTC + 5] Pakistan Standard Time, Yekaterinburg Standard Time' - case '5.5': - return 'Asia/Kolkata'; //'[UTC + 5:30] Indian Standard Time, Sri Lanka Time' - case '5.75': - return 'Asia/Kathmandu'; //'[UTC + 5:45] Nepal Time' - case '6': - return 'Etc/GMT-' . $offset; //'[UTC + 6] Bangladesh Time, Bhutan Time, Novosibirsk Standard Time' - case '6.5': - return 'Indian/Cocos'; //'[UTC + 6:30] Cocos Islands Time, Myanmar Time' - case '7': - return 'Etc/GMT-' . $offset; //'[UTC + 7] Indochina Time, Krasnoyarsk Standard Time' - case '8': - return 'Etc/GMT-' . $offset; //'[UTC + 8] Chinese Standard Time, Australian Western Standard Time, Irkutsk Standard Time' - case '8.75': - return 'Australia/Eucla'; //'[UTC + 8:45] Southeastern Western Australia Standard Time' - case '9': - return 'Etc/GMT-' . $offset; //'[UTC + 9] Japan Standard Time, Korea Standard Time, Chita Standard Time' - case '9.5': - return 'Australia/ACT'; //'[UTC + 9:30] Australian Central Standard Time' - case '10': - return 'Etc/GMT-' . $offset; //'[UTC + 10] Australian Eastern Standard Time, Vladivostok Standard Time' - case '10.5': - return 'Australia/Lord_Howe'; //'[UTC + 10:30] Lord Howe Standard Time' - case '11': - return 'Etc/GMT-' . $offset; //'[UTC + 11] Solomon Island Time, Magadan Standard Time' - case '11.5': - return 'Pacific/Norfolk'; //'[UTC + 11:30] Norfolk Island Time' - case '12': - return 'Etc/GMT-12'; //'[UTC + 12] New Zealand Time, Fiji Time, Kamchatka Standard Time' - case '12.75': - return 'Pacific/Chatham'; //'[UTC + 12:45] Chatham Islands Time' - case '13': - return 'Pacific/Tongatapu'; //'[UTC + 13] Tonga Time, Phoenix Islands Time' - case '14': - return 'Pacific/Kiritimati'; //'[UTC + 14] Line Island Time' - default: - return 'UTC'; - } - } -} diff --git a/phpBB/includes/db/migration/data/310/timezone_p2.php b/phpBB/includes/db/migration/data/310/timezone_p2.php deleted file mode 100644 index 113b979e4f..0000000000 --- a/phpBB/includes/db/migration/data/310/timezone_p2.php +++ /dev/null @@ -1,43 +0,0 @@ -db_tools->sql_column_exists($this->table_prefix . 'users', 'user_dst'); - } - - static public function depends_on() - { - return array('phpbb_db_migration_data_310_timezone'); - } - - public function update_schema() - { - return array( - 'drop_columns' => array( - $this->table_prefix . 'users' => array( - 'user_dst', - ), - ), - ); - } - - public function revert_schema() - { - return array( - 'add_columns' => array( - $this->table_prefix . 'users' => array( - 'user_dst' => array('BOOL', 0), - ), - ), - ); - } -} diff --git a/phpBB/includes/db/migration/exception.php b/phpBB/includes/db/migration/exception.php deleted file mode 100644 index e84330dd71..0000000000 --- a/phpBB/includes/db/migration/exception.php +++ /dev/null @@ -1,79 +0,0 @@ -parameters = $parameters; - } - - /** - * Output the error as a string - * - * @return string - */ - public function __toString() - { - return $this->message . ': ' . var_export($this->parameters, true); - } - - /** - * Get the parameters - * - * @return array - */ - public function getParameters() - { - return $this->parameters; - } - - /** - * Get localised message (with $user->lang()) - * - * @param phpbb_user $user - * @return string - */ - public function getLocalisedMessage(phpbb_user $user) - { - $parameters = $this->getParameters(); - array_unshift($parameters, $this->getMessage()); - - return call_user_func_array(array($user, 'lang'), $parameters); - } -} diff --git a/phpBB/includes/db/migration/migration.php b/phpBB/includes/db/migration/migration.php deleted file mode 100644 index 0ffa96fd14..0000000000 --- a/phpBB/includes/db/migration/migration.php +++ /dev/null @@ -1,190 +0,0 @@ -sql_query() */ - protected $queries = array(); - - /** - * Constructor - * - * @param phpbb_config $config - * @param phpbb_db_driver $db - * @param phpbb_db_tools $db_tools - * @param string $phpbb_root_path - * @param string $php_ext - * @param string $table_prefix - */ - public function __construct(phpbb_config $config, phpbb_db_driver $db, phpbb_db_tools $db_tools, $phpbb_root_path, $php_ext, $table_prefix) - { - $this->config = $config; - $this->db = $db; - $this->db_tools = $db_tools; - $this->table_prefix = $table_prefix; - - $this->phpbb_root_path = $phpbb_root_path; - $this->php_ext = $php_ext; - - $this->errors = array(); - } - - /** - * Defines other migrations to be applied first - * - * @return array An array of migration class names - */ - static public function depends_on() - { - return array(); - } - - /** - * Allows you to check if the migration is effectively installed (entirely optional) - * - * This is checked when a migration is installed. If true is returned, the migration will be set as - * installed without performing the database changes. - * This function is intended to help moving to migrations from a previous database updater, where some - * migrations may have been installed already even though they are not yet listed in the migrations table. - * - * @return bool True if this migration is installed, False if this migration is not installed (checked on install) - */ - public function effectively_installed() - { - return false; - } - - /** - * Updates the database schema by providing a set of change instructions - * - * @return array Array of schema changes (compatible with db_tools->perform_schema_changes()) - */ - public function update_schema() - { - return array(); - } - - /** - * Reverts the database schema by providing a set of change instructions - * - * @return array Array of schema changes (compatible with db_tools->perform_schema_changes()) - */ - public function revert_schema() - { - return array(); - } - - /** - * Updates data by returning a list of instructions to be executed - * - * @return array Array of data update instructions - */ - public function update_data() - { - return array(); - } - - /** - * Reverts data by returning a list of instructions to be executed - * - * @return array Array of data instructions that will be performed on revert - * NOTE: calls to tools (such as config.add) are automatically reverted when - * possible, so you should not attempt to revert those, this is mostly for - * otherwise unrevertable calls (custom functions for example) - */ - public function revert_data() - { - return array(); - } - - /** - * Wrapper for running queries to generate user feedback on updates - * - * @param string $sql SQL query to run on the database - * @return mixed Query result from db->sql_query() - */ - protected function sql_query($sql) - { - $this->queries[] = $sql; - - $this->db->sql_return_on_error(true); - - if ($sql === 'begin') - { - $result = $this->db->sql_transaction('begin'); - } - else if ($sql === 'commit') - { - $result = $this->db->sql_transaction('commit'); - } - else - { - $result = $this->db->sql_query($sql); - if ($this->db->sql_error_triggered) - { - $this->errors[] = array( - 'sql' => $this->db->sql_error_sql, - 'code' => $this->db->sql_error_returned, - ); - } - } - - $this->db->sql_return_on_error(false); - - return $result; - } - - /** - * Get the list of queries run - * - * @return array - */ - public function get_queries() - { - return $this->queries; - } -} diff --git a/phpBB/includes/db/migration/tool/config.php b/phpBB/includes/db/migration/tool/config.php deleted file mode 100644 index 0b626bf455..0000000000 --- a/phpBB/includes/db/migration/tool/config.php +++ /dev/null @@ -1,150 +0,0 @@ -config = $config; - } - - /** - * {@inheritdoc} - */ - public function get_name() - { - return 'config'; - } - - /** - * Add a config setting. - * - * @param string $config_name The name of the config setting - * you would like to add - * @param mixed $config_value The value of the config setting - * @param bool $is_dynamic True if it is dynamic (changes very often) - * and should not be stored in the cache, false if not. - * @return null - */ - public function add($config_name, $config_value, $is_dynamic = false) - { - if (isset($this->config[$config_name])) - { - return; - } - - $this->config->set($config_name, $config_value, !$is_dynamic); - } - - /** - * Update an existing config setting. - * - * @param string $config_name The name of the config setting you would - * like to update - * @param mixed $config_value The value of the config setting - * @return null - */ - public function update($config_name, $config_value) - { - if (!isset($this->config[$config_name])) - { - throw new phpbb_db_migration_exception('CONFIG_NOT_EXIST', $config_name); - } - - $this->config->set($config_name, $config_value); - } - - /** - * Update a config setting if the first argument equal to the - * current config value - * - * @param string $compare If equal to the current config value, will be - * updated to the new config value, otherwise not - * @param string $config_name The name of the config setting you would - * like to update - * @param mixed $config_value The value of the config setting - * @return null - */ - public function update_if_equals($compare, $config_name, $config_value) - { - if (!isset($this->config[$config_name])) - { - throw new phpbb_db_migration_exception('CONFIG_NOT_EXIST', $config_name); - } - - $this->config->set_atomic($config_name, $compare, $config_value); - } - - /** - * Remove an existing config setting. - * - * @param string $config_name The name of the config setting you would - * like to remove - * @return null - */ - public function remove($config_name) - { - if (!isset($this->config[$config_name])) - { - return; - } - - $this->config->delete($config_name); - } - - /** - * {@inheritdoc} - */ - public function reverse() - { - $arguments = func_get_args(); - $original_call = array_shift($arguments); - - $call = false; - switch ($original_call) - { - case 'add': - $call = 'remove'; - break; - - case 'remove': - $call = 'add'; - break; - - case 'update_if_equals': - $call = 'update_if_equals'; - - // Set to the original value if the current value is what we compared to originally - $arguments = array( - $arguments[2], - $arguments[1], - $arguments[0], - ); - break; - } - - if ($call) - { - return call_user_func_array(array(&$this, $call), $arguments); - } - } -} diff --git a/phpBB/includes/db/migration/tool/interface.php b/phpBB/includes/db/migration/tool/interface.php deleted file mode 100644 index ced53b2023..0000000000 --- a/phpBB/includes/db/migration/tool/interface.php +++ /dev/null @@ -1,33 +0,0 @@ -db = $db; - $this->cache = $cache; - $this->user = $user; - $this->phpbb_root_path = $phpbb_root_path; - $this->php_ext = $php_ext; - $this->modules_table = $modules_table; - } - - /** - * {@inheritdoc} - */ - public function get_name() - { - return 'module'; - } - - /** - * Module Exists - * - * Check if a module exists - * - * @param string $class The module class(acp|mcp|ucp) - * @param int|string|bool $parent The parent module_id|module_langname (0 for no parent). - * Use false to ignore the parent check and check class wide. - * @param int|string $module The module_id|module_langname you would like to - * check for to see if it exists - * @return bool true/false if module exists - */ - public function exists($class, $parent, $module) - { - // the main root directory should return true - if (!$module) - { - return true; - } - - $parent_sql = ''; - if ($parent !== false) - { - // Allows '' to be sent as 0 - $parent = $parent ?: 0; - - if (!is_numeric($parent)) - { - $sql = 'SELECT module_id - FROM ' . $this->modules_table . " - WHERE module_langname = '" . $this->db->sql_escape($parent) . "' - AND module_class = '" . $this->db->sql_escape($class) . "'"; - $result = $this->db->sql_query($sql); - $module_id = $this->db->sql_fetchfield('module_id'); - $this->db->sql_freeresult($result); - - if (!$module_id) - { - return false; - } - - $parent_sql = 'AND parent_id = ' . (int) $module_id; - } - else - { - $parent_sql = 'AND parent_id = ' . (int) $parent; - } - } - - $sql = 'SELECT module_id - FROM ' . $this->modules_table . " - WHERE module_class = '" . $this->db->sql_escape($class) . "' - $parent_sql - AND " . ((is_numeric($module)) ? 'module_id = ' . (int) $module : "module_langname = '" . $this->db->sql_escape($module) . "'"); - $result = $this->db->sql_query($sql); - $module_id = $this->db->sql_fetchfield('module_id'); - $this->db->sql_freeresult($result); - - if ($module_id) - { - return true; - } - - return false; - } - - /** - * Module Add - * - * Add a new module - * - * @param string $class The module class(acp|mcp|ucp) - * @param int|string $parent The parent module_id|module_langname (0 for no parent) - * @param array $data an array of the data on the new module. - * This can be setup in two different ways. - * 1. The "manual" way. For inserting a category or one at a time. - * It will be merged with the base array shown a bit below, - * but at the least requires 'module_langname' to be sent, and, - * if you want to create a module (instead of just a category) you must - * send module_basename and module_mode. - * array( - * 'module_enabled' => 1, - * 'module_display' => 1, - * 'module_basename' => '', - * 'module_class' => $class, - * 'parent_id' => (int) $parent, - * 'module_langname' => '', - * 'module_mode' => '', - * 'module_auth' => '', - * ) - * 2. The "automatic" way. For inserting multiple at a time based on the - * specs in the info file for the module(s). For this to work the - * modules must be correctly setup in the info file. - * An example follows (this would insert the settings, log, and flag - * modes from the includes/acp/info/acp_asacp.php file): - * array( - * 'module_basename' => 'asacp', - * 'modes' => array('settings', 'log', 'flag'), - * ) - * Optionally you may not send 'modes' and it will insert all of the - * modules in that info file. - * @param string|bool $include_path If you would like to use a custom include - * path, specify that here - * @return null - */ - public function add($class, $parent = 0, $data = array(), $include_path = false) - { - // Allows '' to be sent as 0 - $parent = $parent ?: 0; - - // allow sending the name as a string in $data to create a category - if (!is_array($data)) - { - $data = array('module_langname' => $data); - } - - if (!isset($data['module_langname'])) - { - // The "automatic" way - $basename = (isset($data['module_basename'])) ? $data['module_basename'] : ''; - $basename = str_replace(array('/', '\\'), '', $basename); - $class = str_replace(array('/', '\\'), '', $class); - - $module = $this->get_module_info($class, $basename); - - $result = ''; - foreach ($module['modes'] as $mode => $module_info) - { - if (!isset($data['modes']) || in_array($mode, $data['modes'])) - { - $new_module = array( - 'module_basename' => $basename, - 'module_langname' => $module_info['title'], - 'module_mode' => $mode, - 'module_auth' => $module_info['auth'], - 'module_display' => (isset($module_info['display'])) ? $module_info['display'] : true, - 'before' => (isset($module_info['before'])) ? $module_info['before'] : false, - 'after' => (isset($module_info['after'])) ? $module_info['after'] : false, - ); - - // Run the "manual" way with the data we've collected. - $this->add($class, $parent, $new_module); - } - } - - return; - } - - // The "manual" way - if (!is_numeric($parent)) - { - $sql = 'SELECT module_id - FROM ' . $this->modules_table . " - WHERE module_langname = '" . $this->db->sql_escape($parent) . "' - AND module_class = '" . $this->db->sql_escape($class) . "'"; - $result = $this->db->sql_query($sql); - $module_id = $this->db->sql_fetchfield('module_id'); - $this->db->sql_freeresult($result); - - if (!$module_id) - { - throw new phpbb_db_migration_exception('MODULE_NOT_EXIST', $parent); - } - - $parent = $data['parent_id'] = $module_id; - } - else if (!$this->exists($class, false, $parent)) - { - throw new phpbb_db_migration_exception('MODULE_NOT_EXIST', $parent); - } - - if ($this->exists($class, $parent, $data['module_langname'])) - { - return; - } - - if (!class_exists('acp_modules')) - { - include($this->phpbb_root_path . 'includes/acp/acp_modules.' . $this->php_ext); - $this->user->add_lang('acp/modules'); - } - $acp_modules = new acp_modules(); - - $module_data = array( - 'module_enabled' => (isset($data['module_enabled'])) ? $data['module_enabled'] : 1, - 'module_display' => (isset($data['module_display'])) ? $data['module_display'] : 1, - 'module_basename' => (isset($data['module_basename'])) ? $data['module_basename'] : '', - 'module_class' => $class, - 'parent_id' => (int) $parent, - 'module_langname' => (isset($data['module_langname'])) ? $data['module_langname'] : '', - 'module_mode' => (isset($data['module_mode'])) ? $data['module_mode'] : '', - 'module_auth' => (isset($data['module_auth'])) ? $data['module_auth'] : '', - ); - $result = $acp_modules->update_module_data($module_data, true); - - // update_module_data can either return a string or an empty array... - if (is_string($result)) - { - // Error - throw new phpbb_db_migration_exception('MODULE_ERROR', $result); - } - else - { - // Success - $module_log_name = ((isset($this->user->lang[$data['module_langname']])) ? $this->user->lang[$data['module_langname']] : $data['module_langname']); - add_log('admin', 'LOG_MODULE_ADD', $module_log_name); - - // Move the module if requested above/below an existing one - if (isset($data['before']) && $data['before']) - { - $sql = 'SELECT left_id - FROM ' . $this->modules_table . " - WHERE module_class = '" . $this->db->sql_escape($class) . "' - AND parent_id = " . (int) $parent . " - AND module_langname = '" . $this->db->sql_escape($data['before']) . "'"; - $this->db->sql_query($sql); - $to_left = (int) $this->db->sql_fetchfield('left_id'); - - $sql = 'UPDATE ' . $this->modules_table . " - SET left_id = left_id + 2, right_id = right_id + 2 - WHERE module_class = '" . $this->db->sql_escape($class) . "' - AND left_id >= $to_left - AND left_id < {$module_data['left_id']}"; - $this->db->sql_query($sql); - - $sql = 'UPDATE ' . $this->modules_table . " - SET left_id = $to_left, right_id = " . ($to_left + 1) . " - WHERE module_class = '" . $this->db->sql_escape($class) . "' - AND module_id = {$module_data['module_id']}"; - $this->db->sql_query($sql); - } - else if (isset($data['after']) && $data['after']) - { - $sql = 'SELECT right_id - FROM ' . $this->modules_table . " - WHERE module_class = '" . $this->db->sql_escape($class) . "' - AND parent_id = " . (int) $parent . " - AND module_langname = '" . $this->db->sql_escape($data['after']) . "'"; - $this->db->sql_query($sql); - $to_right = (int) $this->db->sql_fetchfield('right_id'); - - $sql = 'UPDATE ' . $this->modules_table . " - SET left_id = left_id + 2, right_id = right_id + 2 - WHERE module_class = '" . $this->db->sql_escape($class) . "' - AND left_id >= $to_right - AND left_id < {$module_data['left_id']}"; - $this->db->sql_query($sql); - - $sql = 'UPDATE ' . $this->modules_table . ' - SET left_id = ' . ($to_right + 1) . ', right_id = ' . ($to_right + 2) . " - WHERE module_class = '" . $this->db->sql_escape($class) . "' - AND module_id = {$module_data['module_id']}"; - $this->db->sql_query($sql); - } - } - - // Clear the Modules Cache - $this->cache->destroy("_modules_$class"); - } - - /** - * Module Remove - * - * Remove a module - * - * @param string $class The module class(acp|mcp|ucp) - * @param int|string|bool $parent The parent module_id|module_langname(0 for no parent). - * Use false to ignore the parent check and check class wide. - * @param int|string $module The module id|module_langname - * @param string|bool $include_path If you would like to use a custom include path, - * specify that here - * @return null - */ - public function remove($class, $parent = 0, $module = '', $include_path = false) - { - // Imitation of module_add's "automatic" and "manual" method so the uninstaller works from the same set of instructions for umil_auto - if (is_array($module)) - { - if (isset($module['module_langname'])) - { - // Manual Method - return $this->remove($class, $parent, $module['module_langname'], $include_path); - } - - // Failed. - if (!isset($module['module_basename'])) - { - throw new phpbb_db_migration_exception('MODULE_NOT_EXIST'); - } - - // Automatic method - $basename = str_replace(array('/', '\\'), '', $module['module_basename']); - $class = str_replace(array('/', '\\'), '', $class); - - $module_info = $this->get_module_info($class, $basename); - - foreach ($module_info['modes'] as $mode => $info) - { - if (!isset($module['modes']) || in_array($mode, $module['modes'])) - { - $this->remove($class, $parent, $info['title']); - } - } - } - else - { - if (!$this->exists($class, $parent, $module)) - { - return; - } - - $parent_sql = ''; - if ($parent !== false) - { - // Allows '' to be sent as 0 - $parent = ($parent) ?: 0; - - if (!is_numeric($parent)) - { - $sql = 'SELECT module_id - FROM ' . $this->modules_table . " - WHERE module_langname = '" . $this->db->sql_escape($parent) . "' - AND module_class = '" . $this->db->sql_escape($class) . "'"; - $result = $this->db->sql_query($sql); - $module_id = $this->db->sql_fetchfield('module_id'); - $this->db->sql_freeresult($result); - - // we know it exists from the module_exists check - $parent_sql = 'AND parent_id = ' . (int) $module_id; - } - else - { - $parent_sql = 'AND parent_id = ' . (int) $parent; - } - } - - $module_ids = array(); - if (!is_numeric($module)) - { - $sql = 'SELECT module_id - FROM ' . $this->modules_table . " - WHERE module_langname = '" . $this->db->sql_escape($module) . "' - AND module_class = '" . $this->db->sql_escape($class) . "' - $parent_sql"; - $result = $this->db->sql_query($sql); - while ($module_id = $this->db->sql_fetchfield('module_id')) - { - $module_ids[] = (int) $module_id; - } - $this->db->sql_freeresult($result); - - $module_name = $module; - } - else - { - $module = (int) $module; - $sql = 'SELECT module_langname - FROM ' . $this->modules_table . " - WHERE module_id = $module - AND module_class = '" . $this->db->sql_escape($class) . "' - $parent_sql"; - $result = $this->db->sql_query($sql); - $module_name = $this->db->sql_fetchfield('module_id'); - $this->db->sql_freeresult($result); - - $module_ids[] = $module; - } - - if (!class_exists('acp_modules')) - { - include($this->phpbb_root_path . 'includes/acp/acp_modules.' . $this->php_ext); - $this->user->add_lang('acp/modules'); - } - $acp_modules = new acp_modules(); - $acp_modules->module_class = $class; - - foreach ($module_ids as $module_id) - { - $result = $acp_modules->delete_module($module_id); - if (!empty($result)) - { - return; - } - } - - $this->cache->destroy("_modules_$class"); - } - } - - /** - * {@inheritdoc} - */ - public function reverse() - { - $arguments = func_get_args(); - $original_call = array_shift($arguments); - - $call = false; - switch ($original_call) - { - case 'add': - $call = 'remove'; - break; - - case 'remove': - $call = 'add'; - break; - } - - if ($call) - { - return call_user_func_array(array(&$this, $call), $arguments); - } - } - - /** - * Wrapper for acp_modules::get_module_infos() - * - * @param string $class Module Class - * @param string $basename Module Basename - * @return array Module Information - */ - protected function get_module_info($class, $basename) - { - if (!class_exists('acp_modules')) - { - include($this->phpbb_root_path . 'includes/acp/acp_modules.' . $this->php_ext); - } - $acp_modules = new acp_modules(); - $module = $acp_modules->get_module_infos($basename, $class, true); - - if (empty($module)) - { - throw new phpbb_db_migration_exception('MODULE_INFO_FILE_NOT_EXIST', $class, $basename); - } - - return array_pop($module); - } -} diff --git a/phpBB/includes/db/migration/tool/permission.php b/phpBB/includes/db/migration/tool/permission.php deleted file mode 100644 index 2f09c0ac72..0000000000 --- a/phpBB/includes/db/migration/tool/permission.php +++ /dev/null @@ -1,622 +0,0 @@ -db = $db; - $this->cache = $cache; - $this->auth = $auth; - $this->phpbb_root_path = $phpbb_root_path; - $this->php_ext = $php_ext; - } - - /** - * {@inheritdoc} - */ - public function get_name() - { - return 'permission'; - } - - /** - * Permission Exists - * - * Check if a permission (auth) setting exists - * - * @param string $auth_option The name of the permission (auth) option - * @param bool $global True for checking a global permission setting, - * False for a local permission setting - * @return bool true if it exists, false if not - */ - public function exists($auth_option, $global = true) - { - if ($global) - { - $type_sql = ' AND is_global = 1'; - } - else - { - $type_sql = ' AND is_local = 1'; - } - - $sql = 'SELECT auth_option_id - FROM ' . ACL_OPTIONS_TABLE . " - WHERE auth_option = '" . $this->db->sql_escape($auth_option) . "'" - . $type_sql; - $result = $this->db->sql_query($sql); - - $row = $this->db->sql_fetchrow($result); - $this->db->sql_freeresult($result); - - if ($row) - { - return true; - } - - return false; - } - - /** - * Permission Add - * - * Add a permission (auth) option - * - * @param string $auth_option The name of the permission (auth) option - * @param bool $global True for checking a global permission setting, - * False for a local permission setting - * @return null - */ - public function add($auth_option, $global = true, $copy_from = false) - { - if ($this->exists($auth_option, $global)) - { - return; - } - - // We've added permissions, so set to true to notify the user. - $this->permissions_added = true; - - if (!class_exists('auth_admin')) - { - include($this->phpbb_root_path . 'includes/acp/auth.' . $this->php_ext); - } - $auth_admin = new auth_admin(); - - // We have to add a check to see if the !$global (if global, local, and if local, global) permission already exists. If it does, acl_add_option currently has a bug which would break the ACL system, so we are having a work-around here. - if ($this->exists($auth_option, !$global)) - { - $sql_ary = array( - 'is_global' => 1, - 'is_local' => 1, - ); - $sql = 'UPDATE ' . ACL_OPTIONS_TABLE . ' - SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . " - WHERE auth_option = '" . $this->db->sql_escape($auth_option) . "'"; - $this->db->sql_query($sql); - } - else - { - if ($global) - { - $auth_admin->acl_add_option(array('global' => array($auth_option))); - } - else - { - $auth_admin->acl_add_option(array('local' => array($auth_option))); - } - } - - // The permission has been added, now we can copy it if needed - if ($copy_from && isset($auth_admin->acl_options['id'][$copy_from])) - { - $old_id = $auth_admin->acl_options['id'][$copy_from]; - $new_id = $auth_admin->acl_options['id'][$auth_option]; - - $tables = array(ACL_GROUPS_TABLE, ACL_ROLES_DATA_TABLE, ACL_USERS_TABLE); - - foreach ($tables as $table) - { - $sql = 'SELECT * - FROM ' . $table . ' - WHERE auth_option_id = ' . $old_id; - $result = $this->db->sql_query($sql); - - $sql_ary = array(); - while ($row = $this->db->sql_fetchrow($result)) - { - $row['auth_option_id'] = $new_id; - $sql_ary[] = $row; - } - $this->db->sql_freeresult($result); - - if (!empty($sql_ary)) - { - $this->db->sql_multi_insert($table, $sql_ary); - } - } - - $auth_admin->acl_clear_prefetch(); - } - } - - /** - * Permission Remove - * - * Remove a permission (auth) option - * - * @param string $auth_option The name of the permission (auth) option - * @param bool $global True for checking a global permission setting, - * False for a local permission setting - * @return null - */ - public function remove($auth_option, $global = true) - { - if (!$this->exists($auth_option, $global)) - { - return; - } - - if ($global) - { - $type_sql = ' AND is_global = 1'; - } - else - { - $type_sql = ' AND is_local = 1'; - } - $sql = 'SELECT auth_option_id, is_global, is_local - FROM ' . ACL_OPTIONS_TABLE . " - WHERE auth_option = '" . $this->db->sql_escape($auth_option) . "'" . - $type_sql; - $result = $this->db->sql_query($sql); - $row = $this->db->sql_fetchrow($result); - $this->db->sql_freeresult($result); - - $id = (int) $row['auth_option_id']; - - // If it is a local and global permission, do not remove the row! :P - if ($row['is_global'] && $row['is_local']) - { - $sql = 'UPDATE ' . ACL_OPTIONS_TABLE . ' - SET ' . (($global) ? 'is_global = 0' : 'is_local = 0') . ' - WHERE auth_option_id = ' . $id; - $this->db->sql_query($sql); - } - else - { - // Delete time - $tables = array(ACL_GROUPS_TABLE, ACL_ROLES_DATA_TABLE, ACL_USERS_TABLE, ACL_OPTIONS_TABLE); - foreach ($tables as $table) - { - $this->db->sql_query('DELETE FROM ' . $table . ' - WHERE auth_option_id = ' . $id); - } - } - - // Purge the auth cache - $this->cache->destroy('_acl_options'); - $this->auth->acl_clear_prefetch(); - } - - /** - * Add a new permission role - * - * @param string $role_name The new role name - * @param sting $role_type The type (u_, m_, a_) - * @return null - */ - public function role_add($role_name, $role_type, $role_description = '') - { - $sql = 'SELECT role_id - FROM ' . ACL_ROLES_TABLE . " - WHERE role_name = '" . $this->db->sql_escape($role_name) . "'"; - $this->db->sql_query($sql); - $role_id = (int) $this->db->sql_fetchfield('role_id'); - - if ($role_id) - { - return; - } - - $sql = 'SELECT MAX(role_order) AS max_role_order - FROM ' . ACL_ROLES_TABLE . " - WHERE role_type = '" . $this->db->sql_escape($role_type) . "'"; - $this->db->sql_query($sql); - $role_order = (int) $this->db->sql_fetchfield('max_role_order'); - $role_order = (!$role_order) ? 1 : $role_order + 1; - - $sql_ary = array( - 'role_name' => $role_name, - 'role_description' => $role_description, - 'role_type' => $role_type, - 'role_order' => $role_order, - ); - - $sql = 'INSERT INTO ' . ACL_ROLES_TABLE . ' ' . $this->db->sql_build_array('INSERT', $sql_ary); - $this->db->sql_query($sql); - } - - /** - * Update the name on a permission role - * - * @param string $old_role_name The old role name - * @param string $new_role_name The new role name - * @return null - */ - public function role_update($old_role_name, $new_role_name) - { - $sql = 'SELECT role_id - FROM ' . ACL_ROLES_TABLE . " - WHERE role_name = '" . $this->db->sql_escape($old_role_name) . "'"; - $this->db->sql_query($sql); - $role_id = (int) $this->db->sql_fetchfield('role_id'); - - if (!$role_id) - { - throw new phpbb_db_migration_exception('ROLE_NOT_EXIST', $old_role_name); - } - - $sql = 'UPDATE ' . ACL_ROLES_TABLE . " - SET role_name = '" . $this->db->sql_escape($new_role_name) . "' - WHERE role_name = '" . $this->db->sql_escape($old_role_name) . "'"; - $this->db->sql_query($sql); - } - - /** - * Remove a permission role - * - * @param string $role_name The role name to remove - * @return null - */ - public function role_remove($role_name) - { - $sql = 'SELECT role_id - FROM ' . ACL_ROLES_TABLE . " - WHERE role_name = '" . $this->db->sql_escape($role_name) . "'"; - $this->db->sql_query($sql); - $role_id = (int) $this->db->sql_fetchfield('role_id'); - - if (!$role_id) - { - return; - } - - $sql = 'DELETE FROM ' . ACL_ROLES_DATA_TABLE . ' - WHERE role_id = ' . $role_id; - $this->db->sql_query($sql); - - $sql = 'DELETE FROM ' . ACL_ROLES_TABLE . ' - WHERE role_id = ' . $role_id; - $this->db->sql_query($sql); - - $this->auth->acl_clear_prefetch(); - } - - /** - * Permission Set - * - * Allows you to set permissions for a certain group/role - * - * @param string $name The name of the role/group - * @param string|array $auth_option The auth_option or array of - * auth_options you would like to set - * @param string $type The type (role|group) - * @param bool $has_permission True if you want to give them permission, - * false if you want to deny them permission - * @return null - */ - public function permission_set($name, $auth_option, $type = 'role', $has_permission = true) - { - if (!is_array($auth_option)) - { - $auth_option = array($auth_option); - } - - $new_auth = array(); - $sql = 'SELECT auth_option_id - FROM ' . ACL_OPTIONS_TABLE . ' - WHERE ' . $this->db->sql_in_set('auth_option', $auth_option); - $result = $this->db->sql_query($sql); - while ($row = $this->db->sql_fetchrow($result)) - { - $new_auth[] = (int) $row['auth_option_id']; - } - $this->db->sql_freeresult($result); - - if (empty($new_auth)) - { - return; - } - - $current_auth = array(); - - $type = (string) $type; // Prevent PHP bug. - - switch ($type) - { - case 'role': - $sql = 'SELECT role_id - FROM ' . ACL_ROLES_TABLE . " - WHERE role_name = '" . $this->db->sql_escape($name) . "'"; - $this->db->sql_query($sql); - $role_id = (int) $this->db->sql_fetchfield('role_id'); - - if (!$role_id) - { - throw new phpbb_db_migration_exception('ROLE_NOT_EXIST', $name); - } - - $sql = 'SELECT auth_option_id, auth_setting - FROM ' . ACL_ROLES_DATA_TABLE . ' - WHERE role_id = ' . $role_id; - $result = $this->db->sql_query($sql); - while ($row = $this->db->sql_fetchrow($result)) - { - $current_auth[$row['auth_option_id']] = $row['auth_setting']; - } - $this->db->sql_freeresult($result); - break; - - case 'group': - $sql = 'SELECT group_id - FROM ' . GROUPS_TABLE . " - WHERE group_name = '" . $this->db->sql_escape($name) . "'"; - $this->db->sql_query($sql); - $group_id = (int) $this->db->sql_fetchfield('group_id'); - - if (!$group_id) - { - throw new phpbb_db_migration_exception('GROUP_NOT_EXIST', $name); - } - - // If the group has a role set for them we will add the requested permissions to that role. - $sql = 'SELECT auth_role_id - FROM ' . ACL_GROUPS_TABLE . ' - WHERE group_id = ' . $group_id . ' - AND auth_role_id <> 0 - AND forum_id = 0'; - $this->db->sql_query($sql); - $role_id = (int) $this->db->sql_fetchfield('auth_role_id'); - if ($role_id) - { - $sql = 'SELECT role_name - FROM ' . ACL_ROLES_TABLE . ' - WHERE role_id = ' . $role_id; - $this->db->sql_query($sql); - $role_name = $this->db->sql_fetchfield('role_name'); - - return $this->permission_set($role_name, $auth_option, 'role', $has_permission); - } - - $sql = 'SELECT auth_option_id, auth_setting - FROM ' . ACL_GROUPS_TABLE . ' - WHERE group_id = ' . $group_id; - $result = $this->db->sql_query($sql); - while ($row = $this->db->sql_fetchrow($result)) - { - $current_auth[$row['auth_option_id']] = $row['auth_setting']; - } - $this->db->sql_freeresult($result); - break; - } - - $sql_ary = array(); - switch ($type) - { - case 'role': - foreach ($new_auth as $auth_option_id) - { - if (!isset($current_auth[$auth_option_id])) - { - $sql_ary[] = array( - 'role_id' => $role_id, - 'auth_option_id' => $auth_option_id, - 'auth_setting' => $has_permission, - ); - } - } - - $this->db->sql_multi_insert(ACL_ROLES_DATA_TABLE, $sql_ary); - break; - - case 'group': - foreach ($new_auth as $auth_option_id) - { - if (!isset($current_auth[$auth_option_id])) - { - $sql_ary[] = array( - 'group_id' => $group_id, - 'auth_option_id' => $auth_option_id, - 'auth_setting' => $has_permission, - ); - } - } - - $this->db->sql_multi_insert(ACL_GROUPS_TABLE, $sql_ary); - break; - } - - $this->auth->acl_clear_prefetch(); - } - - /** - * Permission Unset - * - * Allows you to unset (remove) permissions for a certain group/role - * - * @param string $name The name of the role/group - * @param string|array $auth_option The auth_option or array of - * auth_options you would like to set - * @param string $type The type (role|group) - * @return null - */ - public function permission_unset($name, $auth_option, $type = 'role') - { - if (!is_array($auth_option)) - { - $auth_option = array($auth_option); - } - - $to_remove = array(); - $sql = 'SELECT auth_option_id - FROM ' . ACL_OPTIONS_TABLE . ' - WHERE ' . $this->db->sql_in_set('auth_option', $auth_option); - $result = $this->db->sql_query($sql); - while ($row = $this->db->sql_fetchrow($result)) - { - $to_remove[] = (int) $row['auth_option_id']; - } - $this->db->sql_freeresult($result); - - if (empty($to_remove)) - { - return; - } - - $type = (string) $type; // Prevent PHP bug. - - switch ($type) - { - case 'role': - $sql = 'SELECT role_id - FROM ' . ACL_ROLES_TABLE . " - WHERE role_name = '" . $this->db->sql_escape($name) . "'"; - $this->db->sql_query($sql); - $role_id = (int) $this->db->sql_fetchfield('role_id'); - - if (!$role_id) - { - throw new phpbb_db_migration_exception('ROLE_NOT_EXIST', $name); - } - - $sql = 'DELETE FROM ' . ACL_ROLES_DATA_TABLE . ' - WHERE ' . $this->db->sql_in_set('auth_option_id', $to_remove); - $this->db->sql_query($sql); - break; - - case 'group': - $sql = 'SELECT group_id - FROM ' . GROUPS_TABLE . " - WHERE group_name = '" . $this->db->sql_escape($name) . "'"; - $this->db->sql_query($sql); - $group_id = (int) $this->db->sql_fetchfield('group_id'); - - if (!$group_id) - { - throw new phpbb_db_migration_exception('GROUP_NOT_EXIST', $name); - } - - // If the group has a role set for them we will remove the requested permissions from that role. - $sql = 'SELECT auth_role_id - FROM ' . ACL_GROUPS_TABLE . ' - WHERE group_id = ' . $group_id . ' - AND auth_role_id <> 0'; - $this->db->sql_query($sql); - $role_id = (int) $this->db->sql_fetchfield('auth_role_id'); - if ($role_id) - { - $sql = 'SELECT role_name - FROM ' . ACL_ROLES_TABLE . ' - WHERE role_id = ' . $role_id; - $this->db->sql_query($sql); - $role_name = $this->db->sql_fetchfield('role_name'); - - return $this->permission_unset($role_name, $auth_option, 'role'); - } - - $sql = 'DELETE FROM ' . ACL_GROUPS_TABLE . ' - WHERE ' . $this->db->sql_in_set('auth_option_id', $to_remove); - $this->db->sql_query($sql); - break; - } - - $this->auth->acl_clear_prefetch(); - } - - /** - * {@inheritdoc} - */ - public function reverse() - { - $arguments = func_get_args(); - $original_call = array_shift($arguments); - - $call = false; - switch ($original_call) - { - case 'add': - $call = 'remove'; - break; - - case 'remove': - $call = 'add'; - break; - - case 'permission_set': - $call = 'permission_unset'; - break; - - case 'permission_unset': - $call = 'permission_set'; - break; - - case 'role_add': - $call = 'role_remove'; - break; - - case 'role_remove': - $call = 'role_add'; - break; - - case 'role_update': - // Set to the original value if the current value is what we compared to originally - $arguments = array( - $arguments[1], - $arguments[0], - ); - break; - } - - if ($call) - { - return call_user_func_array(array(&$this, $call), $arguments); - } - } -} diff --git a/phpBB/includes/db/migrator.php b/phpBB/includes/db/migrator.php deleted file mode 100644 index ca3ffc8043..0000000000 --- a/phpBB/includes/db/migrator.php +++ /dev/null @@ -1,746 +0,0 @@ -config = $config; - $this->db = $db; - $this->db_tools = $db_tools; - - $this->migrations_table = $migrations_table; - - $this->phpbb_root_path = $phpbb_root_path; - $this->php_ext = $php_ext; - - $this->table_prefix = $table_prefix; - - foreach ($tools as $tool) - { - $this->tools[$tool->get_name()] = $tool; - } - - $this->load_migration_state(); - } - - /** - * Loads all migrations and their application state from the database. - * - * @return null - */ - public function load_migration_state() - { - $this->migration_state = array(); - - // prevent errors in case the table does not exist yet - $this->db->sql_return_on_error(true); - - $sql = "SELECT * - FROM " . $this->migrations_table; - $result = $this->db->sql_query($sql); - - if (!$this->db->sql_error_triggered) - { - while ($migration = $this->db->sql_fetchrow($result)) - { - $this->migration_state[$migration['migration_name']] = $migration; - - $this->migration_state[$migration['migration_name']]['migration_depends_on'] = unserialize($migration['migration_depends_on']); - } - } - - $this->db->sql_freeresult($result); - - $this->db->sql_return_on_error(false); - } - - /** - * Sets the list of available migration class names to the given array. - * - * @param array $class_names An array of migration class names - * @return null - */ - public function set_migrations($class_names) - { - $this->migrations = $class_names; - } - - /** - * Runs a single update step from the next migration to be applied. - * - * The update step can either be a schema or a (partial) data update. To - * check if update() needs to be called again use the finished() method. - * - * @return null - */ - public function update() - { - foreach ($this->migrations as $name) - { - if (!isset($this->migration_state[$name]) || - !$this->migration_state[$name]['migration_schema_done'] || - !$this->migration_state[$name]['migration_data_done']) - { - if (!$this->try_apply($name)) - { - continue; - } - else - { - return; - } - } - } - } - - /** - * Attempts to apply a step of the given migration or one of its dependencies - * - * @param string The class name of the migration - * @return bool Whether any update step was successfully run - */ - protected function try_apply($name) - { - if (!class_exists($name)) - { - return false; - } - - $migration = $this->get_migration($name); - - $state = (isset($this->migration_state[$name])) ? - $this->migration_state[$name] : - array( - 'migration_depends_on' => $migration->depends_on(), - 'migration_schema_done' => false, - 'migration_data_done' => false, - 'migration_data_state' => '', - 'migration_start_time' => 0, - 'migration_end_time' => 0, - ); - - foreach ($state['migration_depends_on'] as $depend) - { - if (!isset($this->migration_state[$depend]) || - !$this->migration_state[$depend]['migration_schema_done'] || - !$this->migration_state[$depend]['migration_data_done']) - { - return $this->try_apply($depend); - } - } - - $this->last_run_migration = array( - 'name' => $name, - 'class' => $migration, - 'state' => $state, - ); - - if (!isset($this->migration_state[$name])) - { - if ($migration->effectively_installed()) - { - $state = array( - 'migration_depends_on' => $migration->depends_on(), - 'migration_schema_done' => true, - 'migration_data_done' => true, - 'migration_data_state' => '', - 'migration_start_time' => 0, - 'migration_end_time' => 0, - ); - - $this->last_run_migration['effectively_installed'] = true; - } - else - { - $state['migration_start_time'] = time(); - } - } - - if (!$state['migration_schema_done']) - { - $this->apply_schema_changes($migration->update_schema()); - $state['migration_schema_done'] = true; - } - else if (!$state['migration_data_done']) - { - try - { - $result = $this->process_data_step($migration->update_data(), $state['migration_data_state']); - - $state['migration_data_state'] = ($result === true) ? '' : $result; - $state['migration_data_done'] = ($result === true); - $state['migration_end_time'] = ($result === true) ? time() : 0; - } - catch (phpbb_db_migration_exception $e) - { - // Revert the schema changes - $this->revert($name); - - // Rethrow exception - throw $e; - } - } - - $this->set_migration_state($name, $state); - - return true; - } - - /** - * Runs a single revert step from the last migration installed - * - * YOU MUST ADD/SET ALL MIGRATIONS THAT COULD BE DEPENDENT ON THE MIGRATION TO REVERT TO BEFORE CALLING THIS METHOD! - * The revert step can either be a schema or a (partial) data revert. To - * check if revert() needs to be called again use the migration_state() method. - * - * @param string $migration String migration name to revert (including any that depend on this migration) - * @return null - */ - public function revert($migration) - { - if (!isset($this->migration_state[$migration])) - { - // Not installed - return; - } - - foreach ($this->migration_state as $name => $state) - { - if (!empty($state['migration_depends_on']) && in_array($migration, $state['migration_depends_on'])) - { - $this->revert($name); - } - } - - $this->try_revert($migration); - } - - /** - * Attempts to revert a step of the given migration or one of its dependencies - * - * @param string The class name of the migration - * @return bool Whether any update step was successfully run - */ - protected function try_revert($name) - { - if (!class_exists($name)) - { - return false; - } - - $migration = $this->get_migration($name); - - $state = $this->migration_state[$name]; - - $this->last_run_migration = array( - 'name' => $name, - 'class' => $migration, - ); - - if ($state['migration_data_done']) - { - if ($state['migration_data_state'] !== 'revert_data') - { - $result = $this->process_data_step($migration->update_data(), $state['migration_data_state'], true); - - $state['migration_data_state'] = ($result === true) ? 'revert_data' : $result; - } - else - { - $result = $this->process_data_step($migration->revert_data(), '', false); - - $state['migration_data_state'] = ($result === true) ? '' : $result; - $state['migration_data_done'] = ($result === true) ? false : true; - } - - $this->set_migration_state($name, $state); - } - else - { - $this->apply_schema_changes($migration->revert_schema()); - - $sql = 'DELETE FROM ' . $this->migrations_table . " - WHERE migration_name = '" . $this->db->sql_escape($name) . "'"; - $this->db->sql_query($sql); - - unset($this->migration_state[$name]); - } - - return true; - } - - /** - * Apply schema changes from a migration - * - * Just calls db_tools->perform_schema_changes - * - * @param array $schema_changes from migration - */ - protected function apply_schema_changes($schema_changes) - { - $this->db_tools->perform_schema_changes($schema_changes); - } - - /** - * Process the data step of the migration - * - * @param array $steps The steps to run - * @param bool|string $state Current state of the migration - * @param bool $revert true to revert a data step - * @return bool|string migration state. True if completed, serialized array if not finished - */ - protected function process_data_step($steps, $state, $revert = false) - { - $state = ($state) ? unserialize($state) : false; - - // reverse order of steps if reverting - if ($revert === true) - { - $steps = array_reverse($steps); - } - - foreach ($steps as $step_identifier => $step) - { - $last_result = false; - if ($state) - { - // Continue until we reach the step that matches the last step called - if ($state['step'] != $step_identifier) - { - continue; - } - - // We send the result from last time to the callable function - $last_result = $state['result']; - - // Set state to false since we reached the point we were at - $state = false; - } - - try - { - // Result will be null or true if everything completed correctly - $result = $this->run_step($step, $last_result, $revert); - if ($result !== null && $result !== true) - { - return serialize(array( - 'result' => $result, - 'step' => $step_identifier, - )); - } - } - catch (phpbb_db_migration_exception $e) - { - // We should try rolling back here - foreach ($steps as $reverse_step_identifier => $reverse_step) - { - // If we've reached the current step we can break because we reversed everything that was run - if ($reverse_step_identifier == $step_identifier) - { - break; - } - - // Reverse the step that was run - $result = $this->run_step($reverse_step, false, !$revert); - } - - // rethrow the exception - throw $e; - } - } - - return true; - } - - /** - * Run a single step - * - * An exception should be thrown if an error occurs - * - * @param mixed $step Data step from migration - * @param mixed $last_result Result to pass to the callable (only for 'custom' method) - * @param bool $reverse False to install, True to attempt uninstallation by reversing the call - * @return null - */ - protected function run_step($step, $last_result = false, $reverse = false) - { - $callable_and_parameters = $this->get_callable_from_step($step, $last_result, $reverse); - - if ($callable_and_parameters === false) - { - return; - } - - $callable = $callable_and_parameters[0]; - $parameters = $callable_and_parameters[1]; - - return call_user_func_array($callable, $parameters); - } - - /** - * Get a callable statement from a data step - * - * @param array $step Data step from migration - * @param mixed $last_result Result to pass to the callable (only for 'custom' method) - * @param bool $reverse False to install, True to attempt uninstallation by reversing the call - * @return array Array with parameters for call_user_func_array(), 0 is the callable, 1 is parameters - */ - protected function get_callable_from_step(array $step, $last_result = false, $reverse = false) - { - $type = $step[0]; - $parameters = $step[1]; - - $parts = explode('.', $type); - - $class = $parts[0]; - $method = false; - - if (isset($parts[1])) - { - $method = $parts[1]; - } - - switch ($class) - { - case 'if': - if (!isset($parameters[0])) - { - throw new phpbb_db_migration_exception('MIGRATION_INVALID_DATA_MISSING_CONDITION', $step); - } - - if (!isset($parameters[1])) - { - throw new phpbb_db_migration_exception('MIGRATION_INVALID_DATA_MISSING_STEP', $step); - } - - $condition = $parameters[0]; - - if (!$condition) - { - return false; - } - - $step = $parameters[1]; - - return $this->get_callable_from_step($step); - break; - case 'custom': - if (!is_callable($parameters[0])) - { - throw new phpbb_db_migration_exception('MIGRATION_INVALID_DATA_CUSTOM_NOT_CALLABLE', $step); - } - - return array( - $parameters[0], - array($last_result), - ); - break; - - default: - if (!$method) - { - throw new phpbb_db_migration_exception('MIGRATION_INVALID_DATA_UNKNOWN_TYPE', $step); - } - - if (!isset($this->tools[$class])) - { - throw new phpbb_db_migration_exception('MIGRATION_INVALID_DATA_UNDEFINED_TOOL', $step); - } - - if (!method_exists(get_class($this->tools[$class]), $method)) - { - throw new phpbb_db_migration_exception('MIGRATION_INVALID_DATA_UNDEFINED_METHOD', $step); - } - - // Attempt to reverse operations - if ($reverse) - { - array_unshift($parameters, $method); - - return array( - array($this->tools[$class], 'reverse'), - $parameters, - ); - } - - return array( - array($this->tools[$class], $method), - $parameters, - ); - break; - } - } - - /** - * Insert/Update migration row into the database - * - * @param string $name Name of the migration - * @param array $state - * @return null - */ - protected function set_migration_state($name, $state) - { - $migration_row = $state; - $migration_row['migration_depends_on'] = serialize($state['migration_depends_on']); - - if (isset($this->migration_state[$name])) - { - $sql = 'UPDATE ' . $this->migrations_table . ' - SET ' . $this->db->sql_build_array('UPDATE', $migration_row) . " - WHERE migration_name = '" . $this->db->sql_escape($name) . "'"; - $this->db->sql_query($sql); - } - else - { - $migration_row['migration_name'] = $name; - $sql = 'INSERT INTO ' . $this->migrations_table . ' - ' . $this->db->sql_build_array('INSERT', $migration_row); - $this->db->sql_query($sql); - } - - $this->migration_state[$name] = $state; - - $this->last_run_migration['state'] = $state; - } - - /** - * Checks if a migration's dependencies can even theoretically be satisfied. - * - * @param string $name The class name of the migration - * @return bool|string False if fulfillable, string of missing migration name if unfulfillable - */ - public function unfulfillable($name) - { - if (isset($this->migration_state[$name])) - { - return false; - } - - if (!class_exists($name)) - { - return $name; - } - - $migration = $this->get_migration($name); - $depends = $migration->depends_on(); - - foreach ($depends as $depend) - { - $unfulfillable = $this->unfulfillable($depend); - if ($unfulfillable !== false) - { - return $unfulfillable; - } - } - - return false; - } - - /** - * Checks whether all available, fulfillable migrations have been applied. - * - * @return bool Whether the migrations have been applied - */ - public function finished() - { - foreach ($this->migrations as $name) - { - if (!isset($this->migration_state[$name])) - { - // skip unfulfillable migrations, but fulfillables mean we - // are not finished yet - if ($this->unfulfillable($name) !== false) - { - continue; - } - return false; - } - - $migration = $this->migration_state[$name]; - - if (!$migration['migration_schema_done'] || !$migration['migration_data_done']) - { - return false; - } - } - - return true; - } - - /** - * Gets a migration state (whether it is installed and to what extent) - * - * @param string $migration String migration name to check if it is installed - * @return bool|array False if the migration has not at all been installed, array - */ - public function migration_state($migration) - { - if (!isset($this->migration_state[$migration])) - { - return false; - } - - return $this->migration_state[$migration]; - } - - /** - * Helper to get a migration - * - * @param string $name Name of the migration - * @return phpbb_db_migration - */ - protected function get_migration($name) - { - return new $name($this->config, $this->db, $this->db_tools, $this->phpbb_root_path, $this->php_ext, $this->table_prefix); - } - - /** - * This function adds all migrations sent to it to the migrations table - * - * THIS SHOULD NOT GENERALLY BE USED! THIS IS FOR THE PHPBB INSTALLER. - * THIS WILL THROW ERRORS IF MIGRATIONS ALREADY EXIST IN THE TABLE, DO NOT CALL MORE THAN ONCE! - * - * @param array $migrations Array of migrations (names) to add to the migrations table - * @return null - */ - public function populate_migrations($migrations) - { - foreach ($migrations as $name) - { - if ($this->migration_state($name) === false) - { - $state = array( - 'migration_depends_on' => $name::depends_on(), - 'migration_schema_done' => true, - 'migration_data_done' => true, - 'migration_data_state' => '', - 'migration_start_time' => time(), - 'migration_end_time' => time(), - ); - $this->set_migration_state($name, $state); - } - } - } - - /** - * Load migration data files from a directory - * - * @param phpbb_extension_finder $finder - * @param string $path Path to migration data files - * @param bool $check_fulfillable If TRUE (default), we will check - * if all of the migrations are fulfillable after loading them. - * If FALSE, we will not check. You SHOULD check at least once - * to prevent errors (if including multiple directories, check - * with the last call to prevent throwing errors unnecessarily). - * @return array Array of migration names - */ - public function load_migrations(phpbb_extension_finder $finder, $path, $check_fulfillable = true) - { - if (!is_dir($path)) - { - throw new phpbb_db_migration_exception('DIRECTORY INVALID', $path); - } - - $migrations = array(); - - $files = $finder - ->extension_directory("/") - ->find_from_paths(array('/' => $path)); - foreach ($files as $file) - { - $migrations[$file['path'] . $file['filename']] = ''; - } - $migrations = $finder->get_classes_from_files($migrations); - - foreach ($migrations as $migration) - { - if (!in_array($migration, $this->migrations)) - { - $this->migrations[] = $migration; - } - } - - if ($check_fulfillable) - { - foreach ($this->migrations as $name) - { - $unfulfillable = $this->unfulfillable($name); - if ($unfulfillable !== false) - { - throw new phpbb_db_migration_exception('MIGRATION_NOT_FULFILLABLE', $name, $unfulfillable); - } - } - } - - return $this->migrations; - } -} diff --git a/phpBB/includes/db/sql_insert_buffer.php b/phpBB/includes/db/sql_insert_buffer.php deleted file mode 100644 index c18f908429..0000000000 --- a/phpBB/includes/db/sql_insert_buffer.php +++ /dev/null @@ -1,150 +0,0 @@ -sql_multi_insert() include: -* -* - Going over max packet size of the database connection is usually prevented -* because the data is submitted in batches. -* -* - Reaching database connection timeout is usually prevented because -* submission of batches talks to the database every now and then. -* -* - Usage of less PHP memory because data no longer needed is discarded on -* buffer flush. -* -* Attention: -* Please note that users of this class have to call flush() to flush the -* remaining rows to the database after their batch insert operation is -* finished. -* -* Usage: -* -* $buffer = new phpbb_db_sql_insert_buffer($db, 'test_table', 1234); -* -* while (do_stuff()) -* { -* $buffer->insert(array( -* 'column1' => 'value1', -* 'column2' => 'value2', -* )); -* } -* -* $buffer->flush(); -* -* -* @package dbal -*/ -class phpbb_db_sql_insert_buffer -{ - /** @var phpbb_db_driver */ - protected $db; - - /** @var string */ - protected $table_name; - - /** @var int */ - protected $max_buffered_rows; - - /** @var array */ - protected $buffer = array(); - - /** - * @param phpbb_db_driver $db - * @param string $table_name - * @param int $max_buffered_rows - */ - public function __construct(phpbb_db_driver $db, $table_name, $max_buffered_rows = 500) - { - $this->db = $db; - $this->table_name = $table_name; - $this->max_buffered_rows = $max_buffered_rows; - } - - /** - * Inserts a single row into the buffer if multi insert is supported by the - * database (otherwise an insert query is sent immediately). Then flushes - * the buffer if the number of rows in the buffer is now greater than or - * equal to $max_buffered_rows. - * - * @param array $row - * - * @return bool True when some data was flushed to the database. - * False otherwise. - */ - public function insert(array $row) - { - $this->buffer[] = $row; - - // Flush buffer if it is full or when DB does not support multi inserts. - // In the later case, the buffer will always only contain one row. - if (!$this->db->multi_insert || sizeof($this->buffer) >= $this->max_buffered_rows) - { - return $this->flush(); - } - - return false; - } - - /** - * Inserts a row set, i.e. an array of rows, by calling insert(). - * - * Please note that it is in most cases better to use insert() instead of - * first building a huge rowset. Or at least sizeof($rows) should be kept - * small. - * - * @param array $rows - * - * @return bool True when some data was flushed to the database. - * False otherwise. - */ - public function insert_all(array $rows) - { - // Using bitwise |= because PHP does not have logical ||= - $result = 0; - - foreach ($rows as $row) - { - $result |= (int) $this->insert($row); - } - - return (bool) $result; - } - - /** - * Flushes the buffer content to the DB and clears the buffer. - * - * @return bool True when some data was flushed to the database. - * False otherwise. - */ - public function flush() - { - if (!empty($this->buffer)) - { - $this->db->sql_multi_insert($this->table_name, $this->buffer); - $this->buffer = array(); - - return true; - } - - return false; - } -} diff --git a/phpBB/includes/di/extension/config.php b/phpBB/includes/di/extension/config.php deleted file mode 100644 index 6c272a6588..0000000000 --- a/phpBB/includes/di/extension/config.php +++ /dev/null @@ -1,84 +0,0 @@ -config_file = $config_file; - } - - /** - * Loads a specific configuration. - * - * @param array $config An array of configuration values - * @param ContainerBuilder $container A ContainerBuilder instance - * - * @throws InvalidArgumentException When provided tag is not defined in this extension - */ - public function load(array $config, ContainerBuilder $container) - { - require($this->config_file); - - $container->setParameter('core.adm_relative_path', (isset($phpbb_adm_relative_path) ? $phpbb_adm_relative_path : 'adm/')); - $container->setParameter('core.table_prefix', $table_prefix); - $container->setParameter('cache.driver.class', $this->convert_30_acm_type($acm_type)); - $container->setParameter('dbal.driver.class', phpbb_convert_30_dbms_to_31($dbms)); - $container->setParameter('dbal.dbhost', $dbhost); - $container->setParameter('dbal.dbuser', $dbuser); - $container->setParameter('dbal.dbpasswd', $dbpasswd); - $container->setParameter('dbal.dbname', $dbname); - $container->setParameter('dbal.dbport', $dbport); - $container->setParameter('dbal.new_link', defined('PHPBB_DB_NEW_LINK') && PHPBB_DB_NEW_LINK); - } - - /** - * Returns the recommended alias to use in XML. - * - * This alias is also the mandatory prefix to use when using YAML. - * - * @return string The alias - */ - public function getAlias() - { - return 'config'; - } - - /** - * Convert 3.0 ACM type to 3.1 cache driver class name - * - * @param string $acm_type ACM type - * @return cache driver class - */ - protected function convert_30_acm_type($acm_type) - { - if (preg_match('#^[a-z]+$#', $acm_type)) - { - return 'phpbb_cache_driver_'.$acm_type; - } - - return $acm_type; - } -} diff --git a/phpBB/includes/di/extension/core.php b/phpBB/includes/di/extension/core.php deleted file mode 100644 index 9c36ba2fc4..0000000000 --- a/phpBB/includes/di/extension/core.php +++ /dev/null @@ -1,69 +0,0 @@ -root_path = $root_path; - } - - /** - * Loads a specific configuration. - * - * @param array $config An array of configuration values - * @param ContainerBuilder $container A ContainerBuilder instance - * - * @throws InvalidArgumentException When provided tag is not defined in this extension - */ - public function load(array $config, ContainerBuilder $container) - { - $loader = new YamlFileLoader($container, new FileLocator(phpbb_realpath($this->root_path . 'config'))); - $loader->load('services.yml'); - } - - /** - * Returns the recommended alias to use in XML. - * - * This alias is also the mandatory prefix to use when using YAML. - * - * @return string The alias - */ - public function getAlias() - { - return 'core'; - } -} diff --git a/phpBB/includes/di/extension/ext.php b/phpBB/includes/di/extension/ext.php deleted file mode 100644 index 7d9b433751..0000000000 --- a/phpBB/includes/di/extension/ext.php +++ /dev/null @@ -1,69 +0,0 @@ - $path) - { - $this->paths[] = $path; - } - } - - /** - * Loads a specific configuration. - * - * @param array $config An array of configuration values - * @param ContainerBuilder $container A ContainerBuilder instance - * - * @throws InvalidArgumentException When provided tag is not defined in this extension - */ - public function load(array $config, ContainerBuilder $container) - { - foreach ($this->paths as $path) - { - if (file_exists($path . '/config/services.yml')) - { - $loader = new YamlFileLoader($container, new FileLocator(phpbb_realpath($path . '/config'))); - $loader->load('services.yml'); - } - } - } - - /** - * Returns the recommended alias to use in XML. - * - * This alias is also the mandatory prefix to use when using YAML. - * - * @return string The alias - */ - public function getAlias() - { - return 'ext'; - } -} diff --git a/phpBB/includes/di/pass/collection_pass.php b/phpBB/includes/di/pass/collection_pass.php deleted file mode 100644 index 63a5c7dfc4..0000000000 --- a/phpBB/includes/di/pass/collection_pass.php +++ /dev/null @@ -1,46 +0,0 @@ -findTaggedServiceIds('service_collection') as $id => $data) - { - $definition = $container->getDefinition($id); - - foreach ($container->findTaggedServiceIds($data[0]['tag']) as $service_id => $service_data) - { - $definition->addMethodCall('add', array($service_id)); - } - } - } -} diff --git a/phpBB/includes/di/pass/kernel_pass.php b/phpBB/includes/di/pass/kernel_pass.php deleted file mode 100644 index a701ebcfa6..0000000000 --- a/phpBB/includes/di/pass/kernel_pass.php +++ /dev/null @@ -1,68 +0,0 @@ -getDefinition('dispatcher'); - - foreach ($container->findTaggedServiceIds('kernel.event_listener') as $id => $events) - { - foreach ($events as $event) - { - $priority = isset($event['priority']) ? $event['priority'] : 0; - - if (!isset($event['event'])) - { - throw new InvalidArgumentException(sprintf('Service "%1$s" must define the "event" attribute on "kernel.event_listener" tags.', $id)); - } - - if (!isset($event['method'])) - { - throw new InvalidArgumentException(sprintf('Service "%1$s" must define the "method" attribute on "kernel.event_listener" tags.', $id)); - } - - $definition->addMethodCall('addListenerService', array($event['event'], array($id, $event['method']), $priority)); - } - } - - foreach ($container->findTaggedServiceIds('kernel.event_subscriber') as $id => $attributes) - { - // We must assume that the class value has been correctly filled, even if the service is created by a factory - $class = $container->getDefinition($id)->getClass(); - - $refClass = new ReflectionClass($class); - $interface = 'Symfony\Component\EventDispatcher\EventSubscriberInterface'; - if (!$refClass->implementsInterface($interface)) - { - throw new InvalidArgumentException(sprintf('Service "%1$s" must implement interface "%2$s".', $id, $interface)); - } - - $definition->addMethodCall('addSubscriberService', array($id, $class)); - } - } -} diff --git a/phpBB/includes/di/service_collection.php b/phpBB/includes/di/service_collection.php deleted file mode 100644 index 880cb46d4d..0000000000 --- a/phpBB/includes/di/service_collection.php +++ /dev/null @@ -1,49 +0,0 @@ -container = $container; - } - - /** - * Add a service to the collection - * - * @param string $name The service name - * @return null - */ - public function add($name) - { - $task = $this->container->get($name); - - $this->offsetSet($name, $task); - } -} diff --git a/phpBB/includes/error_collector.php b/phpBB/includes/error_collector.php deleted file mode 100644 index 358da747b8..0000000000 --- a/phpBB/includes/error_collector.php +++ /dev/null @@ -1,62 +0,0 @@ -errors = array(); - } - - function install() - { - set_error_handler(array(&$this, 'error_handler')); - } - - function uninstall() - { - restore_error_handler(); - } - - function error_handler($errno, $msg_text, $errfile, $errline) - { - $this->errors[] = array($errno, $msg_text, $errfile, $errline); - } - - function format_errors() - { - $text = ''; - foreach ($this->errors as $error) - { - if (!empty($text)) - { - $text .= "
\n"; - } - - list($errno, $msg_text, $errfile, $errline) = $error; - - // Prevent leakage of local path to phpBB install - $errfile = phpbb_filter_root_path($errfile); - - $text .= "Errno $errno: $msg_text at $errfile line $errline"; - } - - return $text; - } -} diff --git a/phpBB/includes/event/data.php b/phpBB/includes/event/data.php deleted file mode 100644 index 70718ff0ae..0000000000 --- a/phpBB/includes/event/data.php +++ /dev/null @@ -1,68 +0,0 @@ -set_data($data); - } - - public function set_data(array $data = array()) - { - $this->data = $data; - } - - public function get_data() - { - return $this->data; - } - - /** - * Returns data filtered to only include specified keys. - * - * This effectively discards any keys added to data by hooks. - */ - public function get_data_filtered($keys) - { - return array_intersect_key($this->data, array_flip($keys)); - } - - public function offsetExists($offset) - { - return isset($this->data[$offset]); - } - - public function offsetGet($offset) - { - return isset($this->data[$offset]) ? $this->data[$offset] : null; - } - - public function offsetSet($offset, $value) - { - $this->data[$offset] = $value; - } - - public function offsetUnset($offset) - { - unset($this->data[$offset]); - } -} diff --git a/phpBB/includes/event/dispatcher.php b/phpBB/includes/event/dispatcher.php deleted file mode 100644 index 4f637ce3bb..0000000000 --- a/phpBB/includes/event/dispatcher.php +++ /dev/null @@ -1,42 +0,0 @@ -trigger_event('core.index', compact($vars))); -* -*/ -class phpbb_event_dispatcher extends ContainerAwareEventDispatcher -{ - public function trigger_event($eventName, $data = array()) - { - $event = new phpbb_event_data($data); - $this->dispatch($eventName, $event); - return $event->get_data_filtered(array_keys($data)); - } -} diff --git a/phpBB/includes/event/extension_subscriber_loader.php b/phpBB/includes/event/extension_subscriber_loader.php deleted file mode 100644 index d933b943d7..0000000000 --- a/phpBB/includes/event/extension_subscriber_loader.php +++ /dev/null @@ -1,46 +0,0 @@ -dispatcher = $dispatcher; - $this->extension_manager = $extension_manager; - } - - public function load() - { - $finder = $this->extension_manager->get_finder(); - $subscriber_classes = $finder - ->extension_directory('/event') - ->suffix('listener') - ->core_path('event/') - ->get_classes(); - - foreach ($subscriber_classes as $class) - { - $subscriber = new $class(); - $this->dispatcher->addSubscriber($subscriber); - } - } -} diff --git a/phpBB/includes/event/kernel_exception_subscriber.php b/phpBB/includes/event/kernel_exception_subscriber.php deleted file mode 100644 index f90989a74c..0000000000 --- a/phpBB/includes/event/kernel_exception_subscriber.php +++ /dev/null @@ -1,85 +0,0 @@ -template = $template; - $this->user = $user; - } - - /** - * This listener is run when the KernelEvents::EXCEPTION event is triggered - * - * @param GetResponseForExceptionEvent $event - * @return null - */ - public function on_kernel_exception(GetResponseForExceptionEvent $event) - { - page_header($this->user->lang('INFORMATION')); - - $exception = $event->getException(); - - $this->template->assign_vars(array( - 'MESSAGE_TITLE' => $this->user->lang('INFORMATION'), - 'MESSAGE_TEXT' => $exception->getMessage(), - )); - - $this->template->set_filenames(array( - 'body' => 'message_body.html', - )); - - page_footer(true, false, false); - - - $status_code = $exception instanceof HttpException ? $exception->getStatusCode() : 500; - $response = new Response($this->template->assign_display('body'), $status_code); - $event->setResponse($response); - } - - public static function getSubscribedEvents() - { - return array( - KernelEvents::EXCEPTION => 'on_kernel_exception', - ); - } -} diff --git a/phpBB/includes/event/kernel_request_subscriber.php b/phpBB/includes/event/kernel_request_subscriber.php deleted file mode 100644 index afb8464f80..0000000000 --- a/phpBB/includes/event/kernel_request_subscriber.php +++ /dev/null @@ -1,83 +0,0 @@ -finder = $finder; - $this->root_path = $root_path; - $this->php_ext = $php_ext; - } - - /** - * This listener is run when the KernelEvents::REQUEST event is triggered - * - * This is responsible for setting up the routing information - * - * @param GetResponseEvent $event - * @return null - */ - public function on_kernel_request(GetResponseEvent $event) - { - $request = $event->getRequest(); - $context = new RequestContext(); - $context->fromRequest($request); - - $matcher = phpbb_get_url_matcher($this->finder, $context, $this->root_path, $this->php_ext); - $router_listener = new RouterListener($matcher, $context); - $router_listener->onKernelRequest($event); - } - - public static function getSubscribedEvents() - { - return array( - KernelEvents::REQUEST => 'on_kernel_request', - ); - } -} diff --git a/phpBB/includes/event/kernel_terminate_subscriber.php b/phpBB/includes/event/kernel_terminate_subscriber.php deleted file mode 100644 index 1eaf890e42..0000000000 --- a/phpBB/includes/event/kernel_terminate_subscriber.php +++ /dev/null @@ -1,43 +0,0 @@ - 'on_kernel_terminate', - ); - } -} diff --git a/phpBB/includes/extension/base.php b/phpBB/includes/extension/base.php deleted file mode 100644 index c4462b64d8..0000000000 --- a/phpBB/includes/extension/base.php +++ /dev/null @@ -1,135 +0,0 @@ -container = $container; - $this->extension_finder = $extension_finder; - $this->migrator = $migrator; - - $this->extension_name = $extension_name; - $this->extension_path = $extension_path; - } - - /** - * Single enable step that installs any included migrations - * - * @param mixed $old_state State returned by previous call of this method - * @return false Indicates no further steps are required - */ - public function enable_step($old_state) - { - $migrations = $this->get_migration_file_list(); - - $this->migrator->set_migrations($migrations); - - $this->migrator->update(); - - return !$this->migrator->finished(); - } - - /** - * Single disable step that does nothing - * - * @param mixed $old_state State returned by previous call of this method - * @return false Indicates no further steps are required - */ - public function disable_step($old_state) - { - return false; - } - - /** - * Single purge step that reverts any included and installed migrations - * - * @param mixed $old_state State returned by previous call of this method - * @return false Indicates no further steps are required - */ - public function purge_step($old_state) - { - $migrations = $this->get_migration_file_list(); - - $this->migrator->set_migrations($migrations); - - foreach ($migrations as $migration) - { - while ($this->migrator->migration_state($migration) !== false) - { - $this->migrator->revert($migration); - - return true; - } - } - - return false; - } - - /** - * Get the list of migration files from this extension - * - * @return array - */ - protected function get_migration_file_list() - { - static $migrations = false; - - if ($migrations !== false) - { - return $migrations; - } - - // Only have the finder search in this extension path directory - $migrations = $this->extension_finder - ->extension_directory('/migrations') - ->find_from_extension($this->extension_name, $this->extension_path); - $migrations = $this->extension_finder->get_classes_from_files($migrations); - - return $migrations; - } -} diff --git a/phpBB/includes/extension/exception.php b/phpBB/includes/extension/exception.php deleted file mode 100644 index e08a8912ea..0000000000 --- a/phpBB/includes/extension/exception.php +++ /dev/null @@ -1,27 +0,0 @@ -getMessage(); - } -} \ No newline at end of file diff --git a/phpBB/includes/extension/finder.php b/phpBB/includes/extension/finder.php deleted file mode 100644 index 49bb2a514f..0000000000 --- a/phpBB/includes/extension/finder.php +++ /dev/null @@ -1,523 +0,0 @@ -cached_queries in $this->cache. - * - * Allows the use of multiple differently configured finders with the same cache. - * @var string - */ - protected $cache_name; - - /** - * An associative array, containing all search parameters set in methods. - * @var array - */ - protected $query; - - /** - * A map from md5 hashes of serialized queries to their previously retrieved - * results. - * @var array - */ - protected $cached_queries; - - /** - * Creates a new finder instance with its dependencies - * - * @param phpbb_extension_manager $extension_manager An extension manager - * instance that provides the finder with a list of active - * extensions and their locations - * @param phpbb_filesystem $filesystem Filesystem instance - * @param string $phpbb_root_path Path to the phpbb root directory - * @param phpbb_cache_driver_interface $cache A cache instance or null - * @param string $php_ext php file extension - * @param string $cache_name The name of the cache variable, defaults to - * _ext_finder - */ - public function __construct(phpbb_extension_manager $extension_manager, phpbb_filesystem $filesystem, $phpbb_root_path = '', phpbb_cache_driver_interface $cache = null, $php_ext = 'php', $cache_name = '_ext_finder') - { - $this->extension_manager = $extension_manager; - $this->filesystem = $filesystem; - $this->phpbb_root_path = $phpbb_root_path; - $this->cache = $cache; - $this->php_ext = $php_ext; - $this->cache_name = $cache_name; - - $this->query = array( - 'core_path' => false, - 'core_suffix' => false, - 'core_prefix' => false, - 'core_directory' => false, - 'extension_suffix' => false, - 'extension_prefix' => false, - 'extension_directory' => false, - ); - - $this->cached_queries = ($this->cache) ? $this->cache->get($this->cache_name) : false; - } - - /** - * Sets a core path to be searched in addition to extensions - * - * @param string $core_path The path relative to phpbb_root_path - * @return phpbb_extension_finder This object for chaining calls - */ - public function core_path($core_path) - { - $this->query['core_path'] = $core_path; - return $this; - } - - /** - * Sets the suffix all files found in extensions and core must match. - * - * There is no default file extension, so to find PHP files only, you will - * have to specify .php as a suffix. However when using get_classes, the .php - * file extension is automatically added to suffixes. - * - * @param string $suffix A filename suffix - * @return phpbb_extension_finder This object for chaining calls - */ - public function suffix($suffix) - { - $this->core_suffix($suffix); - $this->extension_suffix($suffix); - return $this; - } - - /** - * Sets a suffix all files found in extensions must match - * - * There is no default file extension, so to find PHP files only, you will - * have to specify .php as a suffix. However when using get_classes, the .php - * file extension is automatically added to suffixes. - * - * @param string $extension_suffix A filename suffix - * @return phpbb_extension_finder This object for chaining calls - */ - public function extension_suffix($extension_suffix) - { - $this->query['extension_suffix'] = $extension_suffix; - return $this; - } - - /** - * Sets a suffix all files found in the core path must match - * - * There is no default file extension, so to find PHP files only, you will - * have to specify .php as a suffix. However when using get_classes, the .php - * file extension is automatically added to suffixes. - * - * @param string $core_suffix A filename suffix - * @return phpbb_extension_finder This object for chaining calls - */ - public function core_suffix($core_suffix) - { - $this->query['core_suffix'] = $core_suffix; - return $this; - } - - /** - * Sets the prefix all files found in extensions and core must match - * - * @param string $prefix A filename prefix - * @return phpbb_extension_finder This object for chaining calls - */ - public function prefix($prefix) - { - $this->core_prefix($prefix); - $this->extension_prefix($prefix); - return $this; - } - - /** - * Sets a prefix all files found in extensions must match - * - * @param string $extension_prefix A filename prefix - * @return phpbb_extension_finder This object for chaining calls - */ - public function extension_prefix($extension_prefix) - { - $this->query['extension_prefix'] = $extension_prefix; - return $this; - } - - /** - * Sets a prefix all files found in the core path must match - * - * @param string $core_prefix A filename prefix - * @return phpbb_extension_finder This object for chaining calls - */ - public function core_prefix($core_prefix) - { - $this->query['core_prefix'] = $core_prefix; - return $this; - } - - /** - * Sets a directory all files found in extensions and core must be contained in - * - * Automatically sets the core_directory if its value does not differ from - * the current directory. - * - * @param string $directory - * @return phpbb_extension_finder This object for chaining calls - */ - public function directory($directory) - { - $this->core_directory($directory); - $this->extension_directory($directory); - return $this; - } - - /** - * Sets a directory all files found in extensions must be contained in - * - * @param string $extension_directory - * @return phpbb_extension_finder This object for chaining calls - */ - public function extension_directory($extension_directory) - { - $this->query['extension_directory'] = $this->sanitise_directory($extension_directory); - return $this; - } - - /** - * Sets a directory all files found in the core path must be contained in - * - * @param string $core_directory - * @return phpbb_extension_finder This object for chaining calls - */ - public function core_directory($core_directory) - { - $this->query['core_directory'] = $this->sanitise_directory($core_directory); - return $this; - } - - /** - * Removes occurances of /./ and makes sure path ends without trailing slash - * - * @param string $directory A directory pattern - * @return string A cleaned up directory pattern - */ - protected function sanitise_directory($directory) - { - $directory = $this->filesystem->clean_path($directory); - $dir_len = strlen($directory); - - if ($dir_len > 1 && $directory[$dir_len - 1] === '/') - { - $directory = substr($directory, 0, -1); - } - - return $directory; - } - - /** - * Finds classes matching the configured options if they follow phpBB naming rules. - * - * The php file extension is automatically added to suffixes. - * - * Note: If a file is matched but contains a class name not following the - * phpBB naming rules an incorrect class name will be returned. - * - * @param bool $cache Whether the result should be cached - * @param bool $use_all_available Use all available instead of just all - * enabled extensions - * @return array An array of found class names - */ - public function get_classes($cache = true, $use_all_available = false) - { - $this->query['extension_suffix'] .= '.' . $this->php_ext; - $this->query['core_suffix'] .= '.' . $this->php_ext; - - $files = $this->find($cache, false, $use_all_available); - - return $this->get_classes_from_files($files); - } - - /** - * Get class names from a list of files - * - * @param array $files Array of files (from find()) - * @return array Array of class names - */ - public function get_classes_from_files($files) - { - $classes = array(); - foreach ($files as $file => $ext_name) - { - $file = preg_replace('#^includes/#', '', $file); - - $classes[] = 'phpbb_' . str_replace('/', '_', substr($file, 0, -strlen('.' . $this->php_ext))); - } - return $classes; - } - - /** - * Finds all directories matching the configured options - * - * @param bool $cache Whether the result should be cached - * @param bool $use_all_available Use all available instead of just all - * enabled extensions - * @param bool $extension_keys Whether the result should have extension name as array key - * @return array An array of paths to found directories - */ - public function get_directories($cache = true, $use_all_available = false, $extension_keys = false) - { - return $this->find_with_root_path($cache, true, $use_all_available, $extension_keys); - } - - /** - * Finds all files matching the configured options. - * - * @param bool $cache Whether the result should be cached - * @param bool $use_all_available Use all available instead of just all - * enabled extensions - * @return array An array of paths to found files - */ - public function get_files($cache = true, $use_all_available = false) - { - return $this->find_with_root_path($cache, false, $use_all_available); - } - - /** - * A wrapper around the general find which prepends a root path to results - * - * @param bool $cache Whether the result should be cached - * @param bool $is_dir Directories will be returned when true, only files - * otherwise - * @param bool $use_all_available Use all available instead of just all - * enabled extensions - * @param bool $extension_keys If true, result will be associative array - * with extension name as key - * @return array An array of paths to found items - */ - protected function find_with_root_path($cache = true, $is_dir = false, $use_all_available = false, $extension_keys = false) - { - $items = $this->find($cache, $is_dir, $use_all_available); - - $result = array(); - foreach ($items as $item => $ext_name) - { - if ($extension_keys) - { - $result[$ext_name] = $this->phpbb_root_path . $item; - } - else - { - $result[] = $this->phpbb_root_path . $item; - } - } - - return $result; - } - - /** - * Finds all file system entries matching the configured options - * - * @param bool $cache Whether the result should be cached - * @param bool $is_dir Directories will be returned when true, only files - * otherwise - * @param bool $use_all_available Use all available instead of just all - * enabled extensions - * @return array An array of paths to found items - */ - public function find($cache = true, $is_dir = false, $use_all_available = false) - { - if ($use_all_available) - { - $extensions = $this->extension_manager->all_available(); - } - else - { - $extensions = $this->extension_manager->all_enabled(); - } - - if ($this->query['core_path']) - { - $extensions['/'] = $this->phpbb_root_path . $this->query['core_path']; - } - - $files = array(); - $file_list = $this->find_from_paths($extensions, $cache, $is_dir); - - foreach ($file_list as $file) - { - $files[$file['named_path']] = $file['ext_name']; - } - - return $files; - } - - /** - * Finds all file system entries matching the configured options for one - * specific extension - * - * @param string $extension_name Name of the extension - * @param string $extension_path Relative path to the extension root directory - * @param bool $cache Whether the result should be cached - * @param bool $is_dir Directories will be returned when true, only files - * otherwise - * @return array An array of paths to found items - */ - public function find_from_extension($extension_name, $extension_path, $cache = true, $is_dir = false) - { - $extensions = array( - $extension_name => $extension_path, - ); - - $files = array(); - $file_list = $this->find_from_paths($extensions, $cache, $is_dir); - - foreach ($file_list as $file) - { - $files[$file['named_path']] = $file['ext_name']; - } - - return $files; - } - - /** - * Finds all file system entries matching the configured options from - * an array of paths - * - * @param array $extensions Array of extensions (name => full relative path) - * @param bool $cache Whether the result should be cached - * @param bool $is_dir Directories will be returned when true, only files - * otherwise - * @return array An array of paths to found items - */ - public function find_from_paths($extensions, $cache = true, $is_dir = false) - { - $this->query['is_dir'] = $is_dir; - $query = md5(serialize($this->query) . serialize($extensions)); - - if (!defined('DEBUG') && $cache && isset($this->cached_queries[$query])) - { - return $this->cached_queries[$query]; - } - - $files = array(); - - foreach ($extensions as $name => $path) - { - $ext_name = $name; - - if (!file_exists($path)) - { - continue; - } - - if ($name === '/') - { - $location = $this->query['core_path']; - $name = ''; - $suffix = $this->query['core_suffix']; - $prefix = $this->query['core_prefix']; - $directory = $this->query['core_directory']; - } - else - { - $location = 'ext/'; - $name .= '/'; - $suffix = $this->query['extension_suffix']; - $prefix = $this->query['extension_prefix']; - $directory = $this->query['extension_directory']; - } - - // match only first directory if leading slash is given - if ($directory === '/') - { - $directory_pattern = '^' . preg_quote(DIRECTORY_SEPARATOR, '#'); - } - else if ($directory && $directory[0] === '/') - { - $directory_pattern = '^' . preg_quote(str_replace('/', DIRECTORY_SEPARATOR, $directory) . DIRECTORY_SEPARATOR, '#'); - } - else - { - $directory_pattern = preg_quote(DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $directory) . DIRECTORY_SEPARATOR, '#'); - } - if ($is_dir) - { - $directory_pattern .= '$'; - } - $directory_pattern = '#' . $directory_pattern . '#'; - - $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path), RecursiveIteratorIterator::SELF_FIRST); - foreach ($iterator as $file_info) - { - $filename = $file_info->getFilename(); - if ($filename == '.' || $filename == '..') - { - continue; - } - - if ($file_info->isDir() == $is_dir) - { - if ($is_dir) - { - $relative_path = $iterator->getInnerIterator()->getSubPath() . DIRECTORY_SEPARATOR . basename($filename) . DIRECTORY_SEPARATOR; - if ($relative_path[0] !== DIRECTORY_SEPARATOR) - { - $relative_path = DIRECTORY_SEPARATOR . $relative_path; - } - } - else - { - $relative_path = DIRECTORY_SEPARATOR . $iterator->getInnerIterator()->getSubPathname(); - } - - if ((!$suffix || substr($relative_path, -strlen($suffix)) === $suffix) && - (!$prefix || substr($filename, 0, strlen($prefix)) === $prefix) && - (!$directory || preg_match($directory_pattern, $relative_path))) - { - $files[] = array( - 'named_path' => str_replace(DIRECTORY_SEPARATOR, '/', $location . $name . substr($relative_path, 1)), - 'ext_name' => $ext_name, - 'path' => str_replace(array(DIRECTORY_SEPARATOR, $this->phpbb_root_path), array('/', ''), $file_info->getPath()) . '/', - 'filename' => $filename, - ); - } - } - } - } - - if ($cache && $this->cache) - { - $this->cached_queries[$query] = $files; - $this->cache->put($this->cache_name, $this->cached_queries); - } - - return $files; - } -} diff --git a/phpBB/includes/extension/interface.php b/phpBB/includes/extension/interface.php deleted file mode 100644 index 7b36a12bf6..0000000000 --- a/phpBB/includes/extension/interface.php +++ /dev/null @@ -1,67 +0,0 @@ -container = $container; - $this->phpbb_root_path = $phpbb_root_path; - $this->db = $db; - $this->config = $config; - $this->cache = $cache; - $this->filesystem = $filesystem; - $this->php_ext = $php_ext; - $this->extension_table = $extension_table; - $this->cache_name = $cache_name; - - $this->extensions = ($this->cache) ? $this->cache->get($this->cache_name) : false; - - if ($this->extensions === false) - { - $this->load_extensions(); - } - } - - /** - * Loads all extension information from the database - * - * @return null - */ - public function load_extensions() - { - $this->extensions = array(); - - // Do not try to load any extensions when installing or updating - // Note: database updater invokes this code, and in 3.0 - // there is no extension table therefore the rest of this function - // fails - if (defined('IN_INSTALL')) - { - return; - } - - $sql = 'SELECT * - FROM ' . $this->extension_table; - - $result = $this->db->sql_query($sql); - $extensions = $this->db->sql_fetchrowset($result); - $this->db->sql_freeresult($result); - - foreach ($extensions as $extension) - { - $extension['ext_path'] = $this->get_extension_path($extension['ext_name']); - $this->extensions[$extension['ext_name']] = $extension; - } - - ksort($this->extensions); - - if ($this->cache) - { - $this->cache->put($this->cache_name, $this->extensions); - } - } - - /** - * Generates the path to an extension - * - * @param string $name The name of the extension - * @param bool $phpbb_relative Whether the path should be relative to phpbb root - * @return string Path to an extension - */ - public function get_extension_path($name, $phpbb_relative = false) - { - $name = str_replace('.', '', $name); - - return (($phpbb_relative) ? $this->phpbb_root_path : '') . 'ext/' . $name . '/'; - } - - /** - * Instantiates the extension meta class for the extension with the given name - * - * @param string $name The extension name - * @return phpbb_extension_interface Instance of the extension meta class or - * phpbb_extension_base if the class does not exist - */ - public function get_extension($name) - { - $extension_class_name = 'phpbb_ext_' . str_replace('/', '_', $name) . '_ext'; - - $migrator = $this->container->get('migrator'); - - if (class_exists($extension_class_name)) - { - return new $extension_class_name($this->container, $this->get_finder(), $migrator, $name, $this->get_extension_path($name, true)); - } - else - { - return new phpbb_extension_base($this->container, $this->get_finder(), $migrator, $name, $this->get_extension_path($name, true)); - } - } - - /** - * Instantiates the metadata manager for the extension with the given name - * - * @param string $name The extension name - * @param string $template The template manager - * @return phpbb_extension_metadata_manager Instance of the metadata manager - */ - public function create_extension_metadata_manager($name, phpbb_template $template) - { - return new phpbb_extension_metadata_manager($name, $this->config, $this, $template, $this->phpbb_root_path); - } - - /** - * Runs a step of the extension enabling process. - * - * Allows the exentension to enable in a long running script that works - * in multiple steps across requests. State is kept for the extension - * in the extensions table. - * - * @param string $name The extension's name - * @return bool False if enabling is finished, true otherwise - */ - public function enable_step($name) - { - // ignore extensions that are already enabled - if (isset($this->extensions[$name]) && $this->extensions[$name]['ext_active']) - { - return false; - } - - $old_state = (isset($this->extensions[$name]['ext_state'])) ? unserialize($this->extensions[$name]['ext_state']) : false; - - $extension = $this->get_extension($name); - $state = $extension->enable_step($old_state); - - $active = ($state === false); - - $extension_data = array( - 'ext_name' => $name, - 'ext_active' => $active, - 'ext_state' => serialize($state), - ); - - $this->extensions[$name] = $extension_data; - $this->extensions[$name]['ext_path'] = $this->get_extension_path($extension_data['ext_name']); - ksort($this->extensions); - - $sql = 'SELECT COUNT(ext_name) as row_count - FROM ' . $this->extension_table . " - WHERE ext_name = '" . $this->db->sql_escape($name) . "'"; - $result = $this->db->sql_query($sql); - $count = $this->db->sql_fetchfield('row_count'); - $this->db->sql_freeresult($result); - - if ($count) - { - $sql = 'UPDATE ' . $this->extension_table . ' - SET ' . $this->db->sql_build_array('UPDATE', $extension_data) . " - WHERE ext_name = '" . $this->db->sql_escape($name) . "'"; - $this->db->sql_query($sql); - } - else - { - $sql = 'INSERT INTO ' . $this->extension_table . ' - ' . $this->db->sql_build_array('INSERT', $extension_data); - $this->db->sql_query($sql); - } - - if ($this->cache) - { - $this->cache->purge(); - } - - return !$active; - } - - /** - * Enables an extension - * - * This method completely enables an extension. But it could be long running - * so never call this in a script that has a max_execution time. - * - * @param string $name The extension's name - * @return null - */ - public function enable($name) - { - while ($this->enable_step($name)); - } - - /** - * Disables an extension - * - * Calls the disable method on the extension's meta class to allow it to - * process the event. - * - * @param string $name The extension's name - * @return bool False if disabling is finished, true otherwise - */ - public function disable_step($name) - { - // ignore extensions that are already disabled - if (!isset($this->extensions[$name]) || !$this->extensions[$name]['ext_active']) - { - return false; - } - - $old_state = unserialize($this->extensions[$name]['ext_state']); - - $extension = $this->get_extension($name); - $state = $extension->disable_step($old_state); - - // continue until the state is false - if ($state !== false) - { - $extension_data = array( - 'ext_state' => serialize($state), - ); - $this->extensions[$name]['ext_state'] = serialize($state); - - $sql = 'UPDATE ' . $this->extension_table . ' - SET ' . $this->db->sql_build_array('UPDATE', $extension_data) . " - WHERE ext_name = '" . $this->db->sql_escape($name) . "'"; - $this->db->sql_query($sql); - - if ($this->cache) - { - $this->cache->purge(); - } - - return true; - } - - $extension_data = array( - 'ext_active' => false, - 'ext_state' => serialize(false), - ); - $this->extensions[$name]['ext_active'] = false; - $this->extensions[$name]['ext_state'] = serialize(false); - - $sql = 'UPDATE ' . $this->extension_table . ' - SET ' . $this->db->sql_build_array('UPDATE', $extension_data) . " - WHERE ext_name = '" . $this->db->sql_escape($name) . "'"; - $this->db->sql_query($sql); - - if ($this->cache) - { - $this->cache->purge(); - } - - return false; - } - - /** - * Disables an extension - * - * Disables an extension completely at once. This process could run for a - * while so never call this in a script that has a max_execution time. - * - * @param string $name The extension's name - * @return null - */ - public function disable($name) - { - while ($this->disable_step($name)); - } - - /** - * Purge an extension - * - * Disables the extension first if active, and then calls purge on the - * extension's meta class to delete the extension's database content. - * - * @param string $name The extension's name - * @return bool False if purging is finished, true otherwise - */ - public function purge_step($name) - { - // ignore extensions that do not exist - if (!isset($this->extensions[$name])) - { - return false; - } - - // disable first if necessary - if ($this->extensions[$name]['ext_active']) - { - $this->disable($name); - } - - $old_state = unserialize($this->extensions[$name]['ext_state']); - - $extension = $this->get_extension($name); - $state = $extension->purge_step($old_state); - - // continue until the state is false - if ($state !== false) - { - $extension_data = array( - 'ext_state' => serialize($state), - ); - $this->extensions[$name]['ext_state'] = serialize($state); - - $sql = 'UPDATE ' . $this->extension_table . ' - SET ' . $this->db->sql_build_array('UPDATE', $extension_data) . " - WHERE ext_name = '" . $this->db->sql_escape($name) . "'"; - $this->db->sql_query($sql); - - if ($this->cache) - { - $this->cache->purge(); - } - - return true; - } - - unset($this->extensions[$name]); - - $sql = 'DELETE FROM ' . $this->extension_table . " - WHERE ext_name = '" . $this->db->sql_escape($name) . "'"; - $this->db->sql_query($sql); - - if ($this->cache) - { - $this->cache->purge(); - } - - return false; - } - - /** - * Purge an extension - * - * Purges an extension completely at once. This process could run for a while - * so never call this in a script that has a max_execution time. - * - * @param string $name The extension's name - * @return null - */ - public function purge($name) - { - while ($this->purge_step($name)); - } - - /** - * Retrieves a list of all available extensions on the filesystem - * - * @return array An array with extension names as keys and paths to the - * extension as values - */ - public function all_available() - { - $available = array(); - if (!is_dir($this->phpbb_root_path . 'ext/')) - { - return $available; - } - - $iterator = new RecursiveIteratorIterator( - new RecursiveDirectoryIterator($this->phpbb_root_path . 'ext/', FilesystemIterator::NEW_CURRENT_AND_KEY | FilesystemIterator::FOLLOW_SYMLINKS), - RecursiveIteratorIterator::SELF_FIRST); - foreach ($iterator as $file_info) - { - if ($file_info->isFile() && $file_info->getFilename() == 'ext.' . $this->php_ext) - { - $ext_name = $iterator->getInnerIterator()->getSubPath(); - - $ext_name = str_replace(DIRECTORY_SEPARATOR, '/', $ext_name); - - $available[$ext_name] = $this->phpbb_root_path . 'ext/' . $ext_name . '/'; - } - } - ksort($available); - return $available; - } - - /** - * Retrieves all configured extensions. - * - * All enabled and disabled extensions are considered configured. A purged - * extension that is no longer in the database is not configured. - * - * @return array An array with extension names as keys and and the - * database stored extension information as values - */ - public function all_configured() - { - $configured = array(); - foreach ($this->extensions as $name => $data) - { - $data['ext_path'] = $this->phpbb_root_path . $data['ext_path']; - $configured[$name] = $data; - } - return $configured; - } - - /** - * Retrieves all enabled extensions. - * - * @return array An array with extension names as keys and and the - * database stored extension information as values - */ - public function all_enabled() - { - $enabled = array(); - foreach ($this->extensions as $name => $data) - { - if ($data['ext_active']) - { - $enabled[$name] = $this->phpbb_root_path . $data['ext_path']; - } - } - return $enabled; - } - - /** - * Retrieves all disabled extensions. - * - * @return array An array with extension names as keys and and the - * database stored extension information as values - */ - public function all_disabled() - { - $disabled = array(); - foreach ($this->extensions as $name => $data) - { - if (!$data['ext_active']) - { - $disabled[$name] = $this->phpbb_root_path . $data['ext_path']; - } - } - return $disabled; - } - - /** - * Check to see if a given extension is available on the filesystem - * - * @param string $name Extension name to check NOTE: Can be user input - * @return bool Depending on whether or not the extension is available - */ - public function available($name) - { - return file_exists($this->get_extension_path($name, true)); - } - - /** - * Check to see if a given extension is enabled - * - * @param string $name Extension name to check - * @return bool Depending on whether or not the extension is enabled - */ - public function enabled($name) - { - return isset($this->extensions[$name]) && $this->extensions[$name]['ext_active']; - } - - /** - * Instantiates a phpbb_extension_finder. - * - * @return phpbb_extension_finder An extension finder instance - */ - public function get_finder() - { - return new phpbb_extension_finder($this, $this->filesystem, $this->phpbb_root_path, $this->cache, $this->php_ext, $this->cache_name . '_finder'); - } -} diff --git a/phpBB/includes/extension/metadata_manager.php b/phpBB/includes/extension/metadata_manager.php deleted file mode 100644 index 14b77c085b..0000000000 --- a/phpBB/includes/extension/metadata_manager.php +++ /dev/null @@ -1,371 +0,0 @@ -config = $config; - $this->extension_manager = $extension_manager; - $this->template = $template; - $this->phpbb_root_path = $phpbb_root_path; - - $this->ext_name = $ext_name; - $this->metadata = array(); - $this->metadata_file = ''; - } - - /** - * Processes and gets the metadata requested - * - * @param string $element All for all metadata that it has and is valid, otherwise specify which section you want by its shorthand term. - * @return array Contains all of the requested metadata, throws an exception on failure - */ - public function get_metadata($element = 'all') - { - $this->set_metadata_file(); - - // Fetch the metadata - $this->fetch_metadata(); - - // Clean the metadata - $this->clean_metadata_array(); - - switch ($element) - { - case 'all': - default: - // Validate the metadata - if (!$this->validate()) - { - return false; - } - - return $this->metadata; - break; - - case 'name': - return ($this->validate('name')) ? $this->metadata['name'] : false; - break; - - case 'display-name': - if (isset($this->metadata['extra']['display-name'])) - { - return $this->metadata['extra']['display-name']; - } - else - { - return ($this->validate('name')) ? $this->metadata['name'] : false; - } - break; - } - } - - /** - * Sets the filepath of the metadata file - * - * @return boolean Set to true if it exists, throws an exception on failure - */ - private function set_metadata_file() - { - $ext_filepath = $this->extension_manager->get_extension_path($this->ext_name); - $metadata_filepath = $this->phpbb_root_path . $ext_filepath . 'composer.json'; - - $this->metadata_file = $metadata_filepath; - - if (!file_exists($this->metadata_file)) - { - throw new phpbb_extension_exception('The required file does not exist: ' . $this->metadata_file); - } - } - - /** - * Gets the contents of the composer.json file - * - * @return bool True if success, throws an exception on failure - */ - private function fetch_metadata() - { - if (!file_exists($this->metadata_file)) - { - throw new phpbb_extension_exception('The required file does not exist: ' . $this->metadata_file); - } - else - { - if (!($file_contents = file_get_contents($this->metadata_file))) - { - throw new phpbb_extension_exception('file_get_contents failed on ' . $this->metadata_file); - } - - if (($metadata = json_decode($file_contents, true)) === NULL) - { - throw new phpbb_extension_exception('json_decode failed on ' . $this->metadata_file); - } - - $this->metadata = $metadata; - - return true; - } - } - - /** - * This array handles the cleaning of the array - * - * @return array Contains the cleaned metadata array - */ - private function clean_metadata_array() - { - return $this->metadata; - } - - /** - * Validate fields - * - * @param string $name ("all" for display and enable validation - * "display" for name, type, and authors - * "name", "type") - * @return Bool True if valid, throws an exception if invalid - */ - public function validate($name = 'display') - { - // Basic fields - $fields = array( - 'name' => '#^[a-zA-Z0-9_\x7f-\xff]{2,}/[a-zA-Z0-9_\x7f-\xff]{2,}$#', - 'type' => '#^phpbb3-extension$#', - 'licence' => '#.+#', - 'version' => '#.+#', - ); - - switch ($name) - { - case 'all': - $this->validate('display'); - - $this->validate_enable(); - break; - - case 'display': - foreach ($fields as $field => $data) - { - $this->validate($field); - } - - $this->validate_authors(); - break; - - default: - if (isset($fields[$name])) - { - if (!isset($this->metadata[$name])) - { - throw new phpbb_extension_exception("Required meta field '$name' has not been set."); - } - - if (!preg_match($fields[$name], $this->metadata[$name])) - { - throw new phpbb_extension_exception("Meta field '$name' is invalid."); - } - } - break; - } - - return true; - } - - /** - * Validates the contents of the authors field - * - * @return boolean True when passes validation, throws exception if invalid - */ - public function validate_authors() - { - if (empty($this->metadata['authors'])) - { - throw new phpbb_extension_exception("Required meta field 'authors' has not been set."); - } - - foreach ($this->metadata['authors'] as $author) - { - if (!isset($author['name'])) - { - throw new phpbb_extension_exception("Required meta field 'author name' has not been set."); - } - } - - return true; - } - - /** - * This array handles the verification that this extension can be enabled on this board - * - * @return bool True if validation succeeded, False if failed - */ - public function validate_enable() - { - // Check for phpBB, PHP versions - if (!$this->validate_require_phpbb() || !$this->validate_require_php()) - { - return false; - } - - return true; - } - - - /** - * Validates the contents of the phpbb requirement field - * - * @return boolean True when passes validation - */ - public function validate_require_phpbb() - { - if (!isset($this->metadata['require']['phpbb'])) - { - return true; - } - - return $this->_validate_version($this->metadata['require']['phpbb'], $this->config['version']); - } - - /** - * Validates the contents of the php requirement field - * - * @return boolean True when passes validation - */ - public function validate_require_php() - { - if (!isset($this->metadata['require']['php'])) - { - return true; - } - - return $this->_validate_version($this->metadata['require']['php'], phpversion()); - } - - /** - * Version validation helper - * - * @param string $string The string for comparing to a version - * @param string $current_version The version to compare to - * @return bool True/False if meets version requirements - */ - private function _validate_version($string, $current_version) - { - // Allow them to specify their own comparison operator (ex: <3.1.2, >=3.1.0) - $comparison_matches = false; - preg_match('#[=<>]+#', $string, $comparison_matches); - - if (!empty($comparison_matches)) - { - return version_compare($current_version, str_replace(array($comparison_matches[0], ' '), '', $string), $comparison_matches[0]); - } - - return version_compare($current_version, $string, '>='); - } - - /** - * Outputs the metadata into the template - * - * @return null - */ - public function output_template_data() - { - $this->template->assign_vars(array( - 'META_NAME' => htmlspecialchars($this->metadata['name']), - 'META_TYPE' => htmlspecialchars($this->metadata['type']), - 'META_DESCRIPTION' => (isset($this->metadata['description'])) ? htmlspecialchars($this->metadata['description']) : '', - 'META_HOMEPAGE' => (isset($this->metadata['homepage'])) ? $this->metadata['homepage'] : '', - 'META_VERSION' => (isset($this->metadata['version'])) ? htmlspecialchars($this->metadata['version']) : '', - 'META_TIME' => (isset($this->metadata['time'])) ? htmlspecialchars($this->metadata['time']) : '', - 'META_LICENCE' => htmlspecialchars($this->metadata['licence']), - - 'META_REQUIRE_PHP' => (isset($this->metadata['require']['php'])) ? htmlspecialchars($this->metadata['require']['php']) : '', - 'META_REQUIRE_PHP_FAIL' => !$this->validate_require_php(), - - 'META_REQUIRE_PHPBB' => (isset($this->metadata['require']['phpbb'])) ? htmlspecialchars($this->metadata['require']['phpbb']) : '', - 'META_REQUIRE_PHPBB_FAIL' => !$this->validate_require_phpbb(), - - 'META_DISPLAY_NAME' => (isset($this->metadata['extra']['display-name'])) ? htmlspecialchars($this->metadata['extra']['display-name']) : '', - )); - - foreach ($this->metadata['authors'] as $author) - { - $this->template->assign_block_vars('meta_authors', array( - 'AUTHOR_NAME' => htmlspecialchars($author['name']), - 'AUTHOR_EMAIL' => (isset($author['email'])) ? $author['email'] : '', - 'AUTHOR_HOMEPAGE' => (isset($author['homepage'])) ? $author['homepage'] : '', - 'AUTHOR_ROLE' => (isset($author['role'])) ? htmlspecialchars($author['role']) : '', - )); - } - } -} diff --git a/phpBB/includes/extension/provider.php b/phpBB/includes/extension/provider.php deleted file mode 100644 index 45b55e5cab..0000000000 --- a/phpBB/includes/extension/provider.php +++ /dev/null @@ -1,76 +0,0 @@ -extension_manager = $extension_manager; - } - - /** - * Finds items using the extension manager. - * - * @return array List of task names - */ - abstract protected function find(); - - /** - * Retrieve an iterator over all items - * - * @return ArrayIterator An iterator for the array of template paths - */ - public function getIterator() - { - if ($this->items === null) - { - $this->items = $this->find(); - } - - return new ArrayIterator($this->items); - } -} diff --git a/phpBB/includes/feed/base.php b/phpBB/includes/feed/base.php deleted file mode 100644 index 296d830932..0000000000 --- a/phpBB/includes/feed/base.php +++ /dev/null @@ -1,261 +0,0 @@ -config = $config; - $this->helper = $helper; - $this->db = $db; - $this->cache = $cache; - $this->user = $user; - $this->auth = $auth; - $this->content_visibility = $content_visibility; - $this->phpEx = $phpEx; - - $this->set_keys(); - - // Allow num_items to be string - if (is_string($this->num_items)) - { - $this->num_items = (int) $this->config[$this->num_items]; - - // A precaution - if (!$this->num_items) - { - $this->num_items = 10; - } - } - } - - /** - * Set keys. - */ - function set_keys() - { - } - - /** - * Open feed - */ - function open() - { - } - - /** - * Close feed - */ - function close() - { - if (!empty($this->result)) - { - $this->db->sql_freeresult($this->result); - } - } - - /** - * Set key - */ - function set($key, $value) - { - $this->keys[$key] = $value; - } - - /** - * Get key - */ - function get($key) - { - return (isset($this->keys[$key])) ? $this->keys[$key] : NULL; - } - - function get_readable_forums() - { - static $forum_ids; - - if (!isset($forum_ids)) - { - $forum_ids = array_keys($this->auth->acl_getf('f_read', true)); - } - - return $forum_ids; - } - - function get_moderator_approve_forums() - { - static $forum_ids; - - if (!isset($forum_ids)) - { - $forum_ids = array_keys($this->auth->acl_getf('m_approve', true)); - } - - return $forum_ids; - } - - function is_moderator_approve_forum($forum_id) - { - static $forum_ids; - - if (!isset($forum_ids)) - { - $forum_ids = array_flip($this->get_moderator_approve_forums()); - } - - return (isset($forum_ids[$forum_id])) ? true : false; - } - - function get_excluded_forums() - { - static $forum_ids; - - // Matches acp/acp_board.php - $cache_name = 'feed_excluded_forum_ids'; - - if (!isset($forum_ids) && ($forum_ids = $this->cache->get('_' . $cache_name)) === false) - { - $sql = 'SELECT forum_id - FROM ' . FORUMS_TABLE . ' - WHERE ' . $this->db->sql_bit_and('forum_options', FORUM_OPTION_FEED_EXCLUDE, '<> 0'); - $result = $this->db->sql_query($sql); - - $forum_ids = array(); - while ($forum_id = (int) $this->db->sql_fetchfield('forum_id')) - { - $forum_ids[$forum_id] = $forum_id; - } - $this->db->sql_freeresult($result); - - $this->cache->put('_' . $cache_name, $forum_ids); - } - - return $forum_ids; - } - - function is_excluded_forum($forum_id) - { - $forum_ids = $this->get_excluded_forums(); - - return isset($forum_ids[$forum_id]) ? true : false; - } - - function get_passworded_forums() - { - return $this->user->get_passworded_forums(); - } - - function get_item() - { - static $result; - - if (!isset($result)) - { - if (!$this->get_sql()) - { - return false; - } - - // Query database - $sql = $this->db->sql_build_query('SELECT', $this->sql); - $result = $this->db->sql_query_limit($sql, $this->num_items); - } - - return $this->db->sql_fetchrow($result); - } - - function user_viewprofile($row) - { - $author_id = (int) $row[$this->get('author_id')]; - - if ($author_id == ANONYMOUS) - { - // Since we cannot link to a profile, we just return GUEST - // instead of $row['username'] - return $this->user->lang['GUEST']; - } - - return '' . $row[$this->get('creator')] . ''; - } -} diff --git a/phpBB/includes/feed/factory.php b/phpBB/includes/feed/factory.php deleted file mode 100644 index 63a1eb8ef0..0000000000 --- a/phpBB/includes/feed/factory.php +++ /dev/null @@ -1,129 +0,0 @@ -container = $container; - $this->config = $config; - $this->db = $db; - } - - /** - * Return correct object for specified mode - * - * @param string $mode The feeds mode. - * @param int $forum_id Forum id specified by the script if forum feed provided. - * @param int $topic_id Topic id specified by the script if topic feed provided. - * - * @return object Returns correct feeds object for specified mode. - */ - function get_feed($mode, $forum_id, $topic_id) - { - switch ($mode) - { - case 'forums': - if (!$this->config['feed_overall_forums']) - { - return false; - } - - return $this->container->get('feed.forums'); - break; - - case 'topics': - case 'topics_new': - if (!$this->config['feed_topics_new']) - { - return false; - } - - return $this->container->get('feed.topics'); - break; - - case 'topics_active': - if (!$this->config['feed_topics_active']) - { - return false; - } - - return $this->container->get('feed.topics_active'); - break; - - case 'news': - // Get at least one news forum - $sql = 'SELECT forum_id - FROM ' . FORUMS_TABLE . ' - WHERE ' . $this->db->sql_bit_and('forum_options', FORUM_OPTION_FEED_NEWS, '<> 0'); - $result = $this->db->sql_query_limit($sql, 1, 0, 600); - $s_feed_news = (int) $this->db->sql_fetchfield('forum_id'); - $this->db->sql_freeresult($result); - - if (!$s_feed_news) - { - return false; - } - - return $this->container->get('feed.news'); - break; - - default: - if ($topic_id && $this->config['feed_topic']) - { - return $this->container->get('feed.topic') - ->set_topic_id($topic_id); - } - else if ($forum_id && $this->config['feed_forum']) - { - return $this->container->get('feed.forum') - ->set_forum_id($forum_id); - } - else if ($this->config['feed_overall']) - { - return $this->container->get('feed.overall'); - } - - return false; - break; - } - } -} diff --git a/phpBB/includes/feed/forum.php b/phpBB/includes/feed/forum.php deleted file mode 100644 index b5f0dd0f8f..0000000000 --- a/phpBB/includes/feed/forum.php +++ /dev/null @@ -1,145 +0,0 @@ -num_items} posts made -* within a specific forum. -* -* @package phpBB3 -*/ -class phpbb_feed_forum extends phpbb_feed_post_base -{ - var $forum_id = 0; - var $forum_data = array(); - - /** - * Set the Forum ID - * - * @param int $forum_id Forum ID - * @return phpbb_feed_forum - */ - public function set_forum_id($topic_id) - { - $this->forum_id = (int) $forum_id; - - return $this; - } - - function open() - { - // Check if forum exists - $sql = 'SELECT forum_id, forum_name, forum_password, forum_type, forum_options - FROM ' . FORUMS_TABLE . ' - WHERE forum_id = ' . $this->forum_id; - $result = $this->db->sql_query($sql); - $this->forum_data = $this->db->sql_fetchrow($result); - $this->db->sql_freeresult($result); - - if (empty($this->forum_data)) - { - trigger_error('NO_FORUM'); - } - - // Forum needs to be postable - if ($this->forum_data['forum_type'] != FORUM_POST) - { - trigger_error('NO_FEED'); - } - - // Make sure forum is not excluded from feed - if (phpbb_optionget(FORUM_OPTION_FEED_EXCLUDE, $this->forum_data['forum_options'])) - { - trigger_error('NO_FEED'); - } - - // Make sure we can read this forum - if (!$this->auth->acl_get('f_read', $this->forum_id)) - { - trigger_error('SORRY_AUTH_READ'); - } - - // Make sure forum is not passworded or user is authed - if ($this->forum_data['forum_password']) - { - $forum_ids_passworded = $this->get_passworded_forums(); - - if (isset($forum_ids_passworded[$this->forum_id])) - { - trigger_error('SORRY_AUTH_READ'); - } - - unset($forum_ids_passworded); - } - } - - function get_sql() - { - // Determine topics with recent activity - $sql = 'SELECT topic_id, topic_last_post_time - FROM ' . TOPICS_TABLE . ' - WHERE forum_id = ' . $this->forum_id . ' - AND topic_moved_id = 0 - AND ' . $this->content_visibility->get_visibility_sql('topic', $this->forum_id) . ' - ORDER BY topic_last_post_time DESC'; - $result = $this->db->sql_query_limit($sql, $this->num_items); - - $topic_ids = array(); - $min_post_time = 0; - while ($row = $this->db->sql_fetchrow()) - { - $topic_ids[] = (int) $row['topic_id']; - - $min_post_time = (int) $row['topic_last_post_time']; - } - $this->db->sql_freeresult($result); - - if (empty($topic_ids)) - { - return false; - } - - $this->sql = array( - 'SELECT' => 'p.post_id, p.topic_id, p.post_time, p.post_edit_time, p.post_visibility, p.post_subject, p.post_text, p.bbcode_bitfield, p.bbcode_uid, p.enable_bbcode, p.enable_smilies, p.enable_magic_url, ' . - 'u.username, u.user_id', - 'FROM' => array( - POSTS_TABLE => 'p', - USERS_TABLE => 'u', - ), - 'WHERE' => $this->db->sql_in_set('p.topic_id', $topic_ids) . ' - AND ' . $this->content_visibility->get_visibility_sql('post', $this->forum_id, 'p.') . ' - AND p.post_time >= ' . $min_post_time . ' - AND p.poster_id = u.user_id', - 'ORDER_BY' => 'p.post_time DESC', - ); - - return true; - } - - function adjust_item(&$item_row, &$row) - { - parent::adjust_item($item_row, $row); - - $item_row['title'] = (isset($row['forum_name']) && $row['forum_name'] !== '') ? $row['forum_name'] . ' ' . $this->separator . ' ' . $item_row['title'] : $item_row['title']; - } - - function get_item() - { - return ($row = parent::get_item()) ? array_merge($this->forum_data, $row) : $row; - } -} diff --git a/phpBB/includes/feed/forums.php b/phpBB/includes/feed/forums.php deleted file mode 100644 index 409097a9f3..0000000000 --- a/phpBB/includes/feed/forums.php +++ /dev/null @@ -1,72 +0,0 @@ -set('title', 'forum_name'); - $this->set('text', 'forum_desc'); - $this->set('bitfield', 'forum_desc_bitfield'); - $this->set('bbcode_uid','forum_desc_uid'); - $this->set('updated', 'forum_last_post_time'); - $this->set('options', 'forum_desc_options'); - } - - function get_sql() - { - $in_fid_ary = array_diff($this->get_readable_forums(), $this->get_excluded_forums()); - if (empty($in_fid_ary)) - { - return false; - } - - // Build SQL Query - $this->sql = array( - 'SELECT' => 'f.forum_id, f.left_id, f.forum_name, f.forum_last_post_time, - f.forum_desc, f.forum_desc_bitfield, f.forum_desc_uid, f.forum_desc_options, - f.forum_topics_approved, f.forum_posts_approved', - 'FROM' => array(FORUMS_TABLE => 'f'), - 'WHERE' => 'f.forum_type = ' . FORUM_POST . ' - AND ' . $this->db->sql_in_set('f.forum_id', $in_fid_ary), - 'ORDER_BY' => 'f.left_id ASC', - ); - - return true; - } - - function adjust_item(&$item_row, &$row) - { - $item_row['link'] = $this->helper->append_sid('viewforum.' . $this->phpEx, 'f=' . $row['forum_id']); - - if ($this->config['feed_item_statistics']) - { - $item_row['statistics'] = $this->user->lang('TOTAL_TOPICS', (int) $row['forum_topics_approved']) - . ' ' . $this->separator_stats . ' ' . $this->user->lang('TOTAL_POSTS_COUNT', (int) $row['forum_posts_approved']); - } - } -} diff --git a/phpBB/includes/feed/helper.php b/phpBB/includes/feed/helper.php deleted file mode 100644 index 93330aa2ad..0000000000 --- a/phpBB/includes/feed/helper.php +++ /dev/null @@ -1,159 +0,0 @@ -config = $config; - $this->user = $user; - $this->phpbb_root_path = $phpbb_root_path; - } - - /** - * Run links through append_sid(), prepend generate_board_url() and remove session id - */ - public function get_board_url() - { - static $board_url; - - if (empty($board_url)) - { - $board_url = generate_board_url(); - } - - return $board_url; - } - - /** - * Run links through append_sid(), prepend generate_board_url() and remove session id - */ - public function append_sid($url, $params) - { - return append_sid($this->get_board_url() . '/' . $url, $params, true, ''); - } - - /** - * Generate ISO 8601 date string (RFC 3339) - */ - public function format_date($time) - { - static $zone_offset; - static $offset_string; - - if (empty($offset_string)) - { - $zone_offset = $this->user->create_datetime()->getOffset(); - $offset_string = phpbb_format_timezone_offset($zone_offset); - } - - return gmdate("Y-m-d\TH:i:s", $time + $zone_offset) . $offset_string; - } - - /** - * Generate text content - */ - public function generate_content($content, $uid, $bitfield, $options) - { - if (empty($content)) - { - return ''; - } - - // Prepare some bbcodes for better parsing - $content = preg_replace("#\[quote(=".*?")?:$uid\]\s*(.*?)\s*\[/quote:$uid\]#si", "[quote$1:$uid]
$2
[/quote:$uid]", $content); - - $content = generate_text_for_display($content, $uid, $bitfield, $options); - - // Add newlines - $content = str_replace('
', '
' . "\n", $content); - - // Convert smiley Relative paths to Absolute path, Windows style - $content = str_replace($this->phpbb_root_path . $this->config['smilies_path'], $this->get_board_url() . '/' . $this->config['smilies_path'], $content); - - // Remove "Select all" link and mouse events - $content = str_replace('' . $this->user->lang['SELECT_ALL_CODE'] . '', '', $content); - $content = preg_replace('#(onkeypress|onclick)="(.*?)"#si', '', $content); - - // Firefox does not support CSS for feeds, though - - // Remove font sizes - // $content = preg_replace('#([^>]+)#iU', '\1', $content); - - // Make text strong :P - // $content = preg_replace('#(.*?)#iU', '\1', $content); - - // Italic - // $content = preg_replace('#([^<]+)#iU', '\1', $content); - - // Underline - // $content = preg_replace('#([^<]+)#iU', '\1', $content); - - // Remove embed Windows Media Streams - $content = preg_replace( '#<\!--\[if \!IE\]>-->([^[]+)<\!--#si', '', $content); - - // Do not use < and >, because we want to retain code contained in [code][/code] - - // Remove embed and objects - $content = preg_replace( '#<(object|embed)(.*?) (value|src)=(.*?) ([^[]+)(object|embed)>#si',' $1 ',$content); - - // Remove some specials html tag, because somewhere there are a mod to allow html tags ;) - $content = preg_replace( '#<(script|iframe)([^[]+)\1>#siU', ' $1 ', $content); - - // Remove Comments from inline attachments [ia] - $content = preg_replace('#
(.*?)(.*?)(.*?)
#si','$4',$content); - - // Replace some entities with their unicode counterpart - $entities = array( - ' ' => "\xC2\xA0", - '•' => "\xE2\x80\xA2", - '·' => "\xC2\xB7", - '©' => "\xC2\xA9", - ); - - $content = str_replace(array_keys($entities), array_values($entities), $content); - - // Remove CDATA blocks. ;) - $content = preg_replace('#\<\!\[CDATA\[(.*?)\]\]\>#s', '', $content); - - // Other control characters - $content = preg_replace('#(?:[\x00-\x1F\x7F]+|(?:\xC2[\x80-\x9F])+)#', '', $content); - - return $content; - } -} diff --git a/phpBB/includes/feed/news.php b/phpBB/includes/feed/news.php deleted file mode 100644 index f2d45b5165..0000000000 --- a/phpBB/includes/feed/news.php +++ /dev/null @@ -1,112 +0,0 @@ -num_items} first posts -* of all topics in the selected news forums. -* -* @package phpBB3 -*/ -class phpbb_feed_news extends phpbb_feed_topic_base -{ - function get_news_forums() - { - static $forum_ids; - - // Matches acp/acp_board.php - $cache_name = 'feed_news_forum_ids'; - - if (!isset($forum_ids) && ($forum_ids = $this->cache->get('_' . $cache_name)) === false) - { - $sql = 'SELECT forum_id - FROM ' . FORUMS_TABLE . ' - WHERE ' . $this->db->sql_bit_and('forum_options', FORUM_OPTION_FEED_NEWS, '<> 0'); - $result = $this->db->sql_query($sql); - - $forum_ids = array(); - while ($forum_id = (int) $this->db->sql_fetchfield('forum_id')) - { - $forum_ids[$forum_id] = $forum_id; - } - $this->db->sql_freeresult($result); - - $this->cache->put('_' . $cache_name, $forum_ids); - } - - return $forum_ids; - } - - function get_sql() - { - // Determine forum ids - $in_fid_ary = array_intersect($this->get_news_forums(), $this->get_readable_forums()); - if (empty($in_fid_ary)) - { - return false; - } - - $in_fid_ary = array_diff($in_fid_ary, $this->get_passworded_forums()); - if (empty($in_fid_ary)) - { - return false; - } - - // We really have to get the post ids first! - $sql = 'SELECT topic_first_post_id, topic_time - FROM ' . TOPICS_TABLE . ' - WHERE ' . $this->db->sql_in_set('forum_id', $in_fid_ary) . ' - AND topic_moved_id = 0 - AND topic_visibility = ' . ITEM_APPROVED . ' - ORDER BY topic_time DESC'; - $result = $this->db->sql_query_limit($sql, $this->num_items); - - $post_ids = array(); - while ($row = $this->db->sql_fetchrow($result)) - { - $post_ids[] = (int) $row['topic_first_post_id']; - } - $this->db->sql_freeresult($result); - - if (empty($post_ids)) - { - return false; - } - - $this->sql = array( - 'SELECT' => 'f.forum_id, f.forum_name, - t.topic_id, t.topic_title, t.topic_poster, t.topic_first_poster_name, t.topic_posts_approved, t.topic_posts_unapproved, t.topic_posts_softdeleted, t.topic_views, t.topic_time, t.topic_last_post_time, - p.post_id, p.post_time, p.post_edit_time, p.post_text, p.bbcode_bitfield, p.bbcode_uid, p.enable_bbcode, p.enable_smilies, p.enable_magic_url', - 'FROM' => array( - TOPICS_TABLE => 't', - POSTS_TABLE => 'p', - ), - 'LEFT_JOIN' => array( - array( - 'FROM' => array(FORUMS_TABLE => 'f'), - 'ON' => 'p.forum_id = f.forum_id', - ), - ), - 'WHERE' => 'p.topic_id = t.topic_id - AND ' . $this->db->sql_in_set('p.post_id', $post_ids), - 'ORDER_BY' => 'p.post_time DESC', - ); - - return true; - } -} diff --git a/phpBB/includes/feed/overall.php b/phpBB/includes/feed/overall.php deleted file mode 100644 index 869df7cde0..0000000000 --- a/phpBB/includes/feed/overall.php +++ /dev/null @@ -1,90 +0,0 @@ -num_items} posts -* from the whole board. -* -* @package phpBB3 -*/ -class phpbb_feed_overall extends phpbb_feed_post_base -{ - function get_sql() - { - $forum_ids = array_diff($this->get_readable_forums(), $this->get_excluded_forums(), $this->get_passworded_forums()); - if (empty($forum_ids)) - { - return false; - } - - // Determine topics with recent activity - $sql = 'SELECT topic_id, topic_last_post_time - FROM ' . TOPICS_TABLE . ' - WHERE topic_moved_id = 0 - AND ' . $this->content_visibility->get_forums_visibility_sql('topic', $forum_ids) . ' - ORDER BY topic_last_post_time DESC'; - $result = $this->db->sql_query_limit($sql, $this->num_items); - - $topic_ids = array(); - $min_post_time = 0; - while ($row = $this->db->sql_fetchrow()) - { - $topic_ids[] = (int) $row['topic_id']; - - $min_post_time = (int) $row['topic_last_post_time']; - } - $this->db->sql_freeresult($result); - - if (empty($topic_ids)) - { - return false; - } - - // Get the actual data - $this->sql = array( - 'SELECT' => 'f.forum_id, f.forum_name, ' . - 'p.post_id, p.topic_id, p.post_time, p.post_edit_time, p.post_visibility, p.post_subject, p.post_text, p.bbcode_bitfield, p.bbcode_uid, p.enable_bbcode, p.enable_smilies, p.enable_magic_url, ' . - 'u.username, u.user_id', - 'FROM' => array( - USERS_TABLE => 'u', - POSTS_TABLE => 'p', - ), - 'LEFT_JOIN' => array( - array( - 'FROM' => array(FORUMS_TABLE => 'f'), - 'ON' => 'f.forum_id = p.forum_id', - ), - ), - 'WHERE' => $this->db->sql_in_set('p.topic_id', $topic_ids) . ' - AND ' . $this->content_visibility->get_visibility_sql('post', array(), 'p.') . ' - AND p.post_time >= ' . $min_post_time . ' - AND u.user_id = p.poster_id', - 'ORDER_BY' => 'p.post_time DESC', - ); - - return true; - } - - function adjust_item(&$item_row, &$row) - { - parent::adjust_item($item_row, $row); - - $item_row['title'] = (isset($row['forum_name']) && $row['forum_name'] !== '') ? $row['forum_name'] . ' ' . $this->separator . ' ' . $item_row['title'] : $item_row['title']; - } -} diff --git a/phpBB/includes/feed/post_base.php b/phpBB/includes/feed/post_base.php deleted file mode 100644 index 1f4cb4b5ef..0000000000 --- a/phpBB/includes/feed/post_base.php +++ /dev/null @@ -1,57 +0,0 @@ -set('title', 'post_subject'); - $this->set('title2', 'topic_title'); - - $this->set('author_id', 'user_id'); - $this->set('creator', 'username'); - $this->set('published', 'post_time'); - $this->set('updated', 'post_edit_time'); - $this->set('text', 'post_text'); - - $this->set('bitfield', 'bbcode_bitfield'); - $this->set('bbcode_uid','bbcode_uid'); - - $this->set('enable_bbcode', 'enable_bbcode'); - $this->set('enable_smilies', 'enable_smilies'); - $this->set('enable_magic_url', 'enable_magic_url'); - } - - function adjust_item(&$item_row, &$row) - { - $item_row['link'] = $this->helper->append_sid('viewtopic.' . $this->phpEx, "t={$row['topic_id']}&p={$row['post_id']}#p{$row['post_id']}"); - - if ($this->config['feed_item_statistics']) - { - $item_row['statistics'] = $this->user->lang['POSTED'] . ' ' . $this->user->lang['POST_BY_AUTHOR'] . ' ' . $this->user_viewprofile($row) - . ' ' . $this->separator_stats . ' ' . $this->user->format_date($row[$this->get('published')]) - . (($this->is_moderator_approve_forum($row['forum_id']) && $row['post_visibility'] !== ITEM_APPROVED) ? ' ' . $this->separator_stats . ' ' . $this->user->lang['POST_UNAPPROVED'] : ''); - } - } -} diff --git a/phpBB/includes/feed/topic.php b/phpBB/includes/feed/topic.php deleted file mode 100644 index 36f958ac60..0000000000 --- a/phpBB/includes/feed/topic.php +++ /dev/null @@ -1,116 +0,0 @@ -num_items} posts made within this topic. -* -* @package phpBB3 -*/ -class phpbb_feed_topic extends phpbb_feed_post_base -{ - var $topic_id = 0; - var $forum_id = 0; - var $topic_data = array(); - - /** - * Set the Topic ID - * - * @param int $topic_id Topic ID - * @return phpbb_feed_topic - */ - public function set_topic_id($topic_id) - { - $this->topic_id = (int) $topic_id; - - return $this; - } - - function open() - { - $sql = 'SELECT f.forum_options, f.forum_password, t.topic_id, t.forum_id, t.topic_visibility, t.topic_title, t.topic_time, t.topic_views, t.topic_replies, t.topic_type - FROM ' . TOPICS_TABLE . ' t - LEFT JOIN ' . FORUMS_TABLE . ' f - ON (f.forum_id = t.forum_id) - WHERE t.topic_id = ' . $this->topic_id; - $result = $this->db->sql_query($sql); - $this->topic_data = $this->db->sql_fetchrow($result); - $this->db->sql_freeresult($result); - - if (empty($this->topic_data)) - { - trigger_error('NO_TOPIC'); - } - - $this->forum_id = (int) $this->topic_data['forum_id']; - - // Make sure topic is either approved or user authed - if (!$this->topic_data['topic_approved'] && !$this->auth->acl_get('m_approve', $this->forum_id)) - { - trigger_error('SORRY_AUTH_READ'); - } - - // Make sure forum is not excluded from feed - if (phpbb_optionget(FORUM_OPTION_FEED_EXCLUDE, $this->topic_data['forum_options'])) - { - trigger_error('NO_FEED'); - } - - // Make sure we can read this forum - if (!$this->auth->acl_get('f_read', $this->forum_id)) - { - trigger_error('SORRY_AUTH_READ'); - } - - // Make sure forum is not passworded or user is authed - if ($this->topic_data['forum_password']) - { - $forum_ids_passworded = $this->get_passworded_forums(); - - if (isset($forum_ids_passworded[$this->forum_id])) - { - trigger_error('SORRY_AUTH_READ'); - } - - unset($forum_ids_passworded); - } - } - - function get_sql() - { - $this->sql = array( - 'SELECT' => 'p.post_id, p.post_time, p.post_edit_time, p.post_visibility, p.post_subject, p.post_text, p.bbcode_bitfield, p.bbcode_uid, p.enable_bbcode, p.enable_smilies, p.enable_magic_url, ' . - 'u.username, u.user_id', - 'FROM' => array( - POSTS_TABLE => 'p', - USERS_TABLE => 'u', - ), - 'WHERE' => 'p.topic_id = ' . $this->topic_id . ' - AND ' . $this->content_visibility->get_visibility_sql('post', $this->forum_id, 'p.') . ' - AND p.poster_id = u.user_id', - 'ORDER_BY' => 'p.post_time DESC', - ); - - return true; - } - - function get_item() - { - return ($row = parent::get_item()) ? array_merge($this->topic_data, $row) : $row; - } -} diff --git a/phpBB/includes/feed/topic_base.php b/phpBB/includes/feed/topic_base.php deleted file mode 100644 index b104a46631..0000000000 --- a/phpBB/includes/feed/topic_base.php +++ /dev/null @@ -1,59 +0,0 @@ -set('title', 'topic_title'); - $this->set('title2', 'forum_name'); - - $this->set('author_id', 'topic_poster'); - $this->set('creator', 'topic_first_poster_name'); - $this->set('published', 'post_time'); - $this->set('updated', 'post_edit_time'); - $this->set('text', 'post_text'); - - $this->set('bitfield', 'bbcode_bitfield'); - $this->set('bbcode_uid','bbcode_uid'); - - $this->set('enable_bbcode', 'enable_bbcode'); - $this->set('enable_smilies', 'enable_smilies'); - $this->set('enable_magic_url', 'enable_magic_url'); - } - - function adjust_item(&$item_row, &$row) - { - $item_row['link'] = $this->helper->append_sid('viewtopic.' . $this->phpEx, 't=' . $row['topic_id'] . '&p=' . $row['post_id'] . '#p' . $row['post_id']); - - if ($this->config['feed_item_statistics']) - { - $item_row['statistics'] = $this->user->lang['POSTED'] . ' ' . $this->user->lang['POST_BY_AUTHOR'] . ' ' . $this->user_viewprofile($row) - . ' ' . $this->separator_stats . ' ' . $this->user->format_date($row[$this->get('published')]) - . ' ' . $this->separator_stats . ' ' . $this->user->lang['REPLIES'] . ' ' . $this->content_visibility->get_count('topic_posts', $row, $row['forum_id']) - 1 - . ' ' . $this->separator_stats . ' ' . $this->user->lang['VIEWS'] . ' ' . $row['topic_views'] - . (($this->is_moderator_approve_forum($row['forum_id']) && $row['topic_posts_unapproved']) ? ' ' . $this->separator_stats . ' ' . $this->user->lang['POSTS_UNAPPROVED'] : ''); - } - } -} diff --git a/phpBB/includes/feed/topics.php b/phpBB/includes/feed/topics.php deleted file mode 100644 index 31f5177773..0000000000 --- a/phpBB/includes/feed/topics.php +++ /dev/null @@ -1,91 +0,0 @@ -num_items} created topics -* including the first post. -* -* @package phpBB3 -*/ -class phpbb_feed_topics extends phpbb_feed_topic_base -{ - function get_sql() - { - $forum_ids_read = $this->get_readable_forums(); - if (empty($forum_ids_read)) - { - return false; - } - - $in_fid_ary = array_diff($forum_ids_read, $this->get_excluded_forums(), $this->get_passworded_forums()); - if (empty($in_fid_ary)) - { - return false; - } - - // We really have to get the post ids first! - $sql = 'SELECT topic_first_post_id, topic_time - FROM ' . TOPICS_TABLE . ' - WHERE ' . $this->db->sql_in_set('forum_id', $in_fid_ary) . ' - AND topic_moved_id = 0 - AND topic_visibility = ' . ITEM_APPROVED . ' - ORDER BY topic_time DESC'; - $result = $this->db->sql_query_limit($sql, $this->num_items); - - $post_ids = array(); - while ($row = $this->db->sql_fetchrow($result)) - { - $post_ids[] = (int) $row['topic_first_post_id']; - } - $this->db->sql_freeresult($result); - - if (empty($post_ids)) - { - return false; - } - - $this->sql = array( - 'SELECT' => 'f.forum_id, f.forum_name, - t.topic_id, t.topic_title, t.topic_poster, t.topic_first_poster_name, t.topic_posts_approved, t.topic_posts_unapproved, t.topic_posts_softdeleted, t.topic_views, t.topic_time, t.topic_last_post_time, - p.post_id, p.post_time, p.post_edit_time, p.post_text, p.bbcode_bitfield, p.bbcode_uid, p.enable_bbcode, p.enable_smilies, p.enable_magic_url', - 'FROM' => array( - TOPICS_TABLE => 't', - POSTS_TABLE => 'p', - ), - 'LEFT_JOIN' => array( - array( - 'FROM' => array(FORUMS_TABLE => 'f'), - 'ON' => 'p.forum_id = f.forum_id', - ), - ), - 'WHERE' => 'p.topic_id = t.topic_id - AND ' . $this->db->sql_in_set('p.post_id', $post_ids), - 'ORDER_BY' => 'p.post_time DESC', - ); - - return true; - } - - function adjust_item(&$item_row, &$row) - { - parent::adjust_item($item_row, $row); - - $item_row['title'] = (isset($row['forum_name']) && $row['forum_name'] !== '') ? $row['forum_name'] . ' ' . $this->separator . ' ' . $item_row['title'] : $item_row['title']; - } -} diff --git a/phpBB/includes/feed/topics_active.php b/phpBB/includes/feed/topics_active.php deleted file mode 100644 index 249dd1d66a..0000000000 --- a/phpBB/includes/feed/topics_active.php +++ /dev/null @@ -1,136 +0,0 @@ -num_items} topics -* with replies made withing the last {$this->sort_days} days -* including the last post. -* -* @package phpBB3 -*/ -class phpbb_feed_topics_active extends phpbb_feed_topic_base -{ - var $sort_days = 7; - - function set_keys() - { - parent::set_keys(); - - $this->set('author_id', 'topic_last_poster_id'); - $this->set('creator', 'topic_last_poster_name'); - } - - function get_sql() - { - $forum_ids_read = $this->get_readable_forums(); - if (empty($forum_ids_read)) - { - return false; - } - - $in_fid_ary = array_intersect($forum_ids_read, $this->get_forum_ids()); - $in_fid_ary = array_diff($in_fid_ary, $this->get_passworded_forums()); - if (empty($in_fid_ary)) - { - return false; - } - - // Search for topics in last X days - $last_post_time_sql = ($this->sort_days) ? ' AND topic_last_post_time > ' . (time() - ($this->sort_days * 24 * 3600)) : ''; - - // We really have to get the post ids first! - $sql = 'SELECT topic_last_post_id, topic_last_post_time - FROM ' . TOPICS_TABLE . ' - WHERE ' . $this->db->sql_in_set('forum_id', $in_fid_ary) . ' - AND topic_moved_id = 0 - AND topic_visibility = ' . ITEM_APPROVED . ' - ' . $last_post_time_sql . ' - ORDER BY topic_last_post_time DESC'; - $result = $this->db->sql_query_limit($sql, $this->num_items); - - $post_ids = array(); - while ($row = $this->db->sql_fetchrow($result)) - { - $post_ids[] = (int) $row['topic_last_post_id']; - } - $this->db->sql_freeresult($result); - - if (empty($post_ids)) - { - return false; - } - - $this->sql = array( - 'SELECT' => 'f.forum_id, f.forum_name, - t.topic_id, t.topic_title, t.topic_posts_approved, t.topic_posts_unapproved, t.topic_posts_softdeleted, t.topic_views, - t.topic_last_poster_id, t.topic_last_poster_name, t.topic_last_post_time, - p.post_id, p.post_time, p.post_edit_time, p.post_text, p.bbcode_bitfield, p.bbcode_uid, p.enable_bbcode, p.enable_smilies, p.enable_magic_url', - 'FROM' => array( - TOPICS_TABLE => 't', - POSTS_TABLE => 'p', - ), - 'LEFT_JOIN' => array( - array( - 'FROM' => array(FORUMS_TABLE => 'f'), - 'ON' => 'p.forum_id = f.forum_id', - ), - ), - 'WHERE' => 'p.topic_id = t.topic_id - AND ' . $this->db->sql_in_set('p.post_id', $post_ids), - 'ORDER_BY' => 'p.post_time DESC', - ); - - return true; - } - - function get_forum_ids() - { - static $forum_ids; - - $cache_name = 'feed_topic_active_forum_ids'; - - if (!isset($forum_ids) && ($forum_ids = $this->cache->get('_' . $cache_name)) === false) - { - $sql = 'SELECT forum_id - FROM ' . FORUMS_TABLE . ' - WHERE forum_type = ' . FORUM_POST . ' - AND ' . $this->db->sql_bit_and('forum_options', FORUM_OPTION_FEED_EXCLUDE, '= 0') . ' - AND ' . $this->db->sql_bit_and('forum_flags', log(FORUM_FLAG_ACTIVE_TOPICS, 2), '<> 0'); - $result = $this->db->sql_query($sql); - - $forum_ids = array(); - while ($forum_id = (int) $this->db->sql_fetchfield('forum_id')) - { - $forum_ids[$forum_id] = $forum_id; - } - $this->db->sql_freeresult($result); - - $this->cache->put('_' . $cache_name, $forum_ids, 180); - } - - return $forum_ids; - } - - function adjust_item(&$item_row, &$row) - { - parent::adjust_item($item_row, $row); - - $item_row['title'] = (isset($row['forum_name']) && $row['forum_name'] !== '') ? $row['forum_name'] . ' ' . $this->separator . ' ' . $item_row['title'] : $item_row['title']; - } -} diff --git a/phpBB/includes/filesystem.php b/phpBB/includes/filesystem.php deleted file mode 100644 index 27cab48fb0..0000000000 --- a/phpBB/includes/filesystem.php +++ /dev/null @@ -1,52 +0,0 @@ -db = $db; - $this->user = $user; - } - - /** - * Returns the group_legend for a given group, if the group exists. - * - * {@inheritDoc} - */ - public function get_group_value($group_id) - { - $sql = 'SELECT group_legend - FROM ' . GROUPS_TABLE . ' - WHERE group_id = ' . (int) $group_id; - $result = $this->db->sql_query($sql); - $current_value = $this->db->sql_fetchfield('group_legend'); - $this->db->sql_freeresult($result); - - if ($current_value === false) - { - // Group not found. - throw new phpbb_groupposition_exception('NO_GROUP'); - } - - return (int) $current_value; - } - - /** - * Get number of groups, displayed on the legend - * - * {@inheritDoc} - */ - public function get_group_count() - { - $sql = 'SELECT group_legend - FROM ' . GROUPS_TABLE . ' - ORDER BY group_legend DESC'; - $result = $this->db->sql_query_limit($sql, 1); - $group_count = (int) $this->db->sql_fetchfield('group_legend'); - $this->db->sql_freeresult($result); - - return $group_count; - } - - /** - * Adds a group by group_id - * - * {@inheritDoc} - */ - public function add_group($group_id) - { - $current_value = $this->get_group_value($group_id); - - if ($current_value == self::GROUP_DISABLED) - { - // Group is currently not displayed, add it at the end. - $next_value = 1 + $this->get_group_count(); - - $sql = 'UPDATE ' . GROUPS_TABLE . ' - SET group_legend = ' . $next_value . ' - WHERE group_legend = ' . self::GROUP_DISABLED . ' - AND group_id = ' . (int) $group_id; - $this->db->sql_query($sql); - return true; - } - - return false; - } - - /** - * Deletes a group by setting the field to self::GROUP_DISABLED and closing the gap in the list. - * - * {@inheritDoc} - */ - public function delete_group($group_id, $skip_group = false) - { - $current_value = $this->get_group_value($group_id); - - if ($current_value != self::GROUP_DISABLED) - { - $this->db->sql_transaction('begin'); - - $sql = 'UPDATE ' . GROUPS_TABLE . ' - SET group_legend = group_legend - 1 - WHERE group_legend > ' . $current_value; - $this->db->sql_query($sql); - - if (!$skip_group) - { - $sql = 'UPDATE ' . GROUPS_TABLE . ' - SET group_legend = ' . self::GROUP_DISABLED . ' - WHERE group_id = ' . (int) $group_id; - $this->db->sql_query($sql); - } - - $this->db->sql_transaction('commit'); - - return true; - } - - return false; - } - - /** - * Moves a group up by group_id - * - * {@inheritDoc} - */ - public function move_up($group_id) - { - return $this->move($group_id, 1); - } - - /** - * Moves a group down by group_id - * - * {@inheritDoc} - */ - public function move_down($group_id) - { - return $this->move($group_id, -1); - } - - /** - * Moves a group up/down - * - * {@inheritDoc} - */ - public function move($group_id, $delta) - { - $delta = (int) $delta; - if (!$delta) - { - return false; - } - - $move_up = ($delta > 0) ? true : false; - $current_value = $this->get_group_value($group_id); - - if ($current_value != self::GROUP_DISABLED) - { - $this->db->sql_transaction('begin'); - - // First we move all groups between our current value and the target value up/down 1, - // so we have a gap for our group to move. - $sql = 'UPDATE ' . GROUPS_TABLE . ' - SET group_legend = group_legend' . (($move_up) ? ' + 1' : ' - 1') . ' - WHERE group_legend > ' . self::GROUP_DISABLED . ' - AND group_legend' . (($move_up) ? ' >= ' : ' <= ') . ($current_value - $delta) . ' - AND group_legend' . (($move_up) ? ' < ' : ' > ') . $current_value; - $this->db->sql_query($sql); - - // Because there might be fewer groups above/below the group than we wanted to move, - // we use the number of changed groups, to update the group. - $delta = (int) $this->db->sql_affectedrows(); - - if ($delta) - { - // And now finally, when we moved some other groups and built a gap, - // we can move the desired group to it. - $sql = 'UPDATE ' . GROUPS_TABLE . ' - SET group_legend = group_legend ' . (($move_up) ? ' - ' : ' + ') . $delta . ' - WHERE group_id = ' . (int) $group_id; - $this->db->sql_query($sql); - - $this->db->sql_transaction('commit'); - - return true; - } - - $this->db->sql_transaction('commit'); - } - - return false; - } - - /** - * Get group type language var - * - * @param int $group_type group_type from the groups-table - * @return string name of the language variable for the given group-type. - */ - static public function group_type_language($group_type) - { - switch ($group_type) - { - case GROUP_OPEN: - return 'GROUP_REQUEST'; - case GROUP_CLOSED: - return 'GROUP_CLOSED'; - case GROUP_HIDDEN: - return 'GROUP_HIDDEN'; - case GROUP_SPECIAL: - return 'GROUP_SPECIAL'; - case GROUP_FREE: - return 'GROUP_OPEN'; - } - } -} diff --git a/phpBB/includes/groupposition/teampage.php b/phpBB/includes/groupposition/teampage.php deleted file mode 100644 index 7c758199e7..0000000000 --- a/phpBB/includes/groupposition/teampage.php +++ /dev/null @@ -1,604 +0,0 @@ -db = $db; - $this->user = $user; - $this->cache = $cache; - } - - /** - * Returns the teampage position for a given group, if the group exists. - * - * {@inheritDoc} - */ - public function get_group_value($group_id) - { - // The join is required to ensure that the group itself exists - $sql = 'SELECT g.group_id, t.teampage_position - FROM ' . GROUPS_TABLE . ' g - LEFT JOIN ' . TEAMPAGE_TABLE . ' t - ON (t.group_id = g.group_id) - WHERE g.group_id = ' . (int) $group_id; - $result = $this->db->sql_query($sql); - $row = $this->db->sql_fetchrow($result); - $this->db->sql_freeresult($result); - - if ($row === false) - { - // Group not found. - throw new phpbb_groupposition_exception('NO_GROUP'); - } - - return (int) $row['teampage_position']; - } - - /** - * Returns the row for a given group, if the group exists. - * - * @param int $group_id group_id of the group to be selected - * @return array Data row of the group - */ - public function get_group_values($group_id) - { - // The join is required to ensure that the group itself exists - $sql = 'SELECT * - FROM ' . GROUPS_TABLE . ' g - LEFT JOIN ' . TEAMPAGE_TABLE . ' t - ON (t.group_id = g.group_id) - WHERE g.group_id = ' . (int) $group_id; - $result = $this->db->sql_query($sql); - $row = $this->db->sql_fetchrow($result); - $this->db->sql_freeresult($result); - - if ($row === false) - { - // Group not found. - throw new phpbb_groupposition_exception('NO_GROUP'); - } - - return $row; - } - - /** - * Returns the teampage position for a given teampage item, if the item exists. - * - * @param int $teampage_id Teampage_id of the selected item - * @return int Teampage position of the item - */ - public function get_teampage_value($teampage_id) - { - $sql = 'SELECT teampage_position - FROM ' . TEAMPAGE_TABLE . ' - WHERE teampage_id = ' . (int) $teampage_id; - $result = $this->db->sql_query($sql); - $current_value = $this->db->sql_fetchfield('teampage_position'); - $this->db->sql_freeresult($result); - - if ($current_value === false) - { - // Group not found. - throw new phpbb_groupposition_exception('NO_GROUP'); - } - - return (int) $current_value; - } - - /** - * Returns the teampage row for a given teampage item, if the item exists. - * - * @param int $teampage_id Teampage_id of the selected item - * @return array Teampage row of the item - */ - public function get_teampage_values($teampage_id) - { - $sql = 'SELECT teampage_position, teampage_parent - FROM ' . TEAMPAGE_TABLE . ' - WHERE teampage_id = ' . (int) $teampage_id; - $result = $this->db->sql_query($sql); - $row = $this->db->sql_fetchrow($result); - $this->db->sql_freeresult($result); - - if ($row === false) - { - // Group not found. - throw new phpbb_groupposition_exception('NO_GROUP'); - } - - return $row; - } - - - /** - * Get number of items displayed - * - * {@inheritDoc} - */ - public function get_group_count() - { - $sql = 'SELECT teampage_position - FROM ' . TEAMPAGE_TABLE . ' - ORDER BY teampage_position DESC'; - $result = $this->db->sql_query_limit($sql, 1); - $group_count = (int) $this->db->sql_fetchfield('teampage_position'); - $this->db->sql_freeresult($result); - - return $group_count; - } - - /** - * Adds a group by group_id - * - * {@inheritDoc} - */ - public function add_group($group_id) - { - return $this->add_group_teampage($group_id, self::NO_PARENT); - } - - /** - * Adds a group by group_id - * - * @param int $group_id group_id of the group to be added - * @param int $parent_id Teampage ID of the parent item - * @return bool True if the group was added successfully - */ - public function add_group_teampage($group_id, $parent_id) - { - $current_value = $this->get_group_value($group_id); - - if ($current_value == self::GROUP_DISABLED) - { - if ($parent_id != self::NO_PARENT) - { - // Check, whether the given parent is a category - $sql = 'SELECT teampage_id - FROM ' . TEAMPAGE_TABLE . ' - WHERE group_id = 0 - AND teampage_id = ' . (int) $parent_id; - $result = $this->db->sql_query_limit($sql, 1); - $parent_is_category = (bool) $this->db->sql_fetchfield('teampage_id'); - $this->db->sql_freeresult($result); - - if ($parent_is_category) - { - // Get value of last child from this parent and add group there - $sql = 'SELECT teampage_position - FROM ' . TEAMPAGE_TABLE . ' - WHERE teampage_parent = ' . (int) $parent_id . ' - OR teampage_id = ' . (int) $parent_id . ' - ORDER BY teampage_position DESC'; - $result = $this->db->sql_query_limit($sql, 1); - $new_position = (int) $this->db->sql_fetchfield('teampage_position'); - $this->db->sql_freeresult($result); - - $sql = 'UPDATE ' . TEAMPAGE_TABLE . ' - SET teampage_position = teampage_position + 1 - WHERE teampage_position > ' . $new_position; - $this->db->sql_query($sql); - } - } - else - { - // Add group at the end - $new_position = $this->get_group_count(); - } - - $sql_ary = array( - 'group_id' => $group_id, - 'teampage_position' => $new_position + 1, - 'teampage_parent' => $parent_id, - ); - - $sql = 'INSERT INTO ' . TEAMPAGE_TABLE . ' ' . $this->db->sql_build_array('INSERT', $sql_ary); - $this->db->sql_query($sql); - - $this->cache->destroy('sql', TEAMPAGE_TABLE); - return true; - } - - $this->cache->destroy('sql', TEAMPAGE_TABLE); - return false; - } - - /** - * Adds a new category - * - * @param string $category_name Name of the category to be added - * @return bool True if the category was added successfully - */ - public function add_category_teampage($category_name) - { - if ($category_name === '') - { - return false; - } - - $num_entries = $this->get_group_count(); - - $sql_ary = array( - 'group_id' => 0, - 'teampage_position' => $num_entries + 1, - 'teampage_parent' => 0, - 'teampage_name' => truncate_string($category_name, 255, 255), - ); - - $sql = 'INSERT INTO ' . TEAMPAGE_TABLE . ' ' . $this->db->sql_build_array('INSERT', $sql_ary); - $this->db->sql_query($sql); - - $this->cache->destroy('sql', TEAMPAGE_TABLE); - return true; - } - - /** - * Deletes a group from the list and closes the gap in the position list. - * - * {@inheritDoc} - */ - public function delete_group($group_id, $skip_group = false) - { - $current_value = $this->get_group_value($group_id); - - if ($current_value != self::GROUP_DISABLED) - { - $sql = 'UPDATE ' . TEAMPAGE_TABLE . ' - SET teampage_position = teampage_position - 1 - WHERE teampage_position > ' . $current_value; - $this->db->sql_query($sql); - - $sql = 'DELETE FROM ' . TEAMPAGE_TABLE . ' - WHERE group_id = ' . $group_id; - $this->db->sql_query($sql); - - $this->cache->destroy('sql', TEAMPAGE_TABLE); - return true; - } - - $this->cache->destroy('sql', TEAMPAGE_TABLE); - return false; - } - - /** - * Deletes an item from the list and closes the gap in the position list. - * - * @param int $teampage_id teampage_id of the item to be deleted - * @param bool $skip_group Skip setting the group to GROUP_DISABLED, to save the query, when you need to update it anyway. - * @return bool True if the item was deleted successfully - */ - public function delete_teampage($teampage_id, $skip_group = false) - { - $current_value = $this->get_teampage_value($teampage_id); - - if ($current_value != self::GROUP_DISABLED) - { - $sql = 'DELETE FROM ' . TEAMPAGE_TABLE . ' - WHERE teampage_id = ' . $teampage_id . ' - OR teampage_parent = ' . $teampage_id; - $this->db->sql_query($sql); - - $delta = (int) $this->db->sql_affectedrows(); - - $sql = 'UPDATE ' . TEAMPAGE_TABLE . ' - SET teampage_position = teampage_position - ' . $delta . ' - WHERE teampage_position > ' . $current_value; - $this->db->sql_query($sql); - - $this->cache->destroy('sql', TEAMPAGE_TABLE); - return true; - } - - $this->cache->destroy('sql', TEAMPAGE_TABLE); - return false; - } - - /** - * Moves a group up by group_id - * - * {@inheritDoc} - */ - public function move_up($group_id) - { - return $this->move($group_id, 1); - } - - /** - * Moves an item up by teampage_id - * - * @param int $group_id group_id of the group to be moved - * @return bool True if the group was moved successfully - */ - public function move_up_teampage($teampage_id) - { - return $this->move_teampage($teampage_id, 1); - } - - /** - * Moves a group down by group_id - * - * {@inheritDoc} - */ - public function move_down($group_id) - { - return $this->move($group_id, -1); - } - - /** - * Movesan item down by teampage_id - * - * @param int $group_id group_id of the group to be moved - * @return bool True if the group was moved successfully - */ - public function move_down_teampage($teampage_id) - { - return $this->move_teampage($teampage_id, -1); - } - - /** - * Moves a group up/down - * - * {@inheritDoc} - */ - public function move($group_id, $delta) - { - $delta = (int) $delta; - if (!$delta) - { - return false; - } - - $move_up = ($delta > 0) ? true : false; - $data = $this->get_group_values($group_id); - - $current_value = (int) $data['teampage_position']; - if ($current_value != self::GROUP_DISABLED) - { - $this->db->sql_transaction('begin'); - - if (!$move_up && $data['teampage_parent'] == self::NO_PARENT) - { - // If we move items down, we need to grab the one sibling more, - // so we do not ignore the children of the previous sibling. - // We will remove the additional sibling later on. - $delta = abs($delta) + 1; - } - - $sql = 'SELECT teampage_position - FROM ' . TEAMPAGE_TABLE . ' - WHERE teampage_parent = ' . (int) $data['teampage_parent'] . ' - AND teampage_position' . (($move_up) ? ' < ' : ' > ') . $current_value . ' - ORDER BY teampage_position' . (($move_up) ? ' DESC' : ' ASC'); - $result = $this->db->sql_query_limit($sql, $delta); - - $sibling_count = 0; - $sibling_limit = $delta; - - // Reset the delta, as we recalculate the new real delta - $delta = 0; - while ($row = $this->db->sql_fetchrow($result)) - { - $sibling_count++; - $delta = $current_value - $row['teampage_position']; - - if (!$move_up && $data['teampage_parent'] == self::NO_PARENT && $sibling_count == $sibling_limit) - { - // Remove the additional sibling we added previously - $delta++; - } - } - $this->db->sql_freeresult($result); - - if ($delta) - { - // First we move all items between our current value and the target value up/down 1, - // so we have a gap for our item to move. - $sql = 'UPDATE ' . TEAMPAGE_TABLE . ' - SET teampage_position = teampage_position' . (($move_up) ? ' + 1' : ' - 1') . ' - WHERE teampage_position' . (($move_up) ? ' >= ' : ' <= ') . ($current_value - $delta) . ' - AND teampage_position' . (($move_up) ? ' < ' : ' > ') . $current_value; - $this->db->sql_query($sql); - - // And now finally, when we moved some other items and built a gap, - // we can move the desired item to it. - $sql = 'UPDATE ' . TEAMPAGE_TABLE . ' - SET teampage_position = teampage_position ' . (($move_up) ? ' - ' : ' + ') . abs($delta) . ' - WHERE group_id = ' . (int) $group_id; - $this->db->sql_query($sql); - - $this->db->sql_transaction('commit'); - $this->cache->destroy('sql', TEAMPAGE_TABLE); - - return true; - } - - $this->db->sql_transaction('commit'); - } - - $this->cache->destroy('sql', TEAMPAGE_TABLE); - return false; - } - - /** - * Moves an item up/down - * - * @param int $teampage_id teampage_id of the item to be moved - * @param int $delta number of steps: - * - positive = move up - * - negative = move down - * @return bool True if the group was moved successfully - */ - public function move_teampage($teampage_id, $delta) - { - $delta = (int) $delta; - if (!$delta) - { - return false; - } - - $move_up = ($delta > 0) ? true : false; - $data = $this->get_teampage_values($teampage_id); - - $current_value = (int) $data['teampage_position']; - if ($current_value != self::GROUP_DISABLED) - { - $this->db->sql_transaction('begin'); - - if (!$move_up && $data['teampage_parent'] == self::NO_PARENT) - { - // If we move items down, we need to grab the one sibling more, - // so we do not ignore the children of the previous sibling. - // We will remove the additional sibling later on. - $delta = abs($delta) + 1; - } - - $sql = 'SELECT teampage_id, teampage_position - FROM ' . TEAMPAGE_TABLE . ' - WHERE teampage_parent = ' . (int) $data['teampage_parent'] . ' - AND teampage_position' . (($move_up) ? ' < ' : ' > ') . $current_value . ' - ORDER BY teampage_position' . (($move_up) ? ' DESC' : ' ASC'); - $result = $this->db->sql_query_limit($sql, $delta); - - $sibling_count = 0; - $sibling_limit = $delta; - - // Reset the delta, as we recalculate the new real delta - $delta = 0; - while ($row = $this->db->sql_fetchrow($result)) - { - $sibling_count++; - $delta = $current_value - $row['teampage_position']; - - // Remove the additional sibling we added previously - // But only, if we included it, this is not be the case - // when we reached the end of our list - if (!$move_up && $data['teampage_parent'] == self::NO_PARENT && $sibling_count == $sibling_limit) - { - $delta++; - } - } - $this->db->sql_freeresult($result); - - if ($delta) - { - $sql = 'SELECT COUNT(teampage_id) as num_items - FROM ' . TEAMPAGE_TABLE . ' - WHERE teampage_id = ' . (int) $teampage_id . ' - OR teampage_parent = ' . (int) $teampage_id; - $result = $this->db->sql_query($sql); - $num_items = (int) $this->db->sql_fetchfield('num_items'); - $this->db->sql_freeresult($result); - - // First we move all items between our current value and the target value up/down 1, - // so we have a gap for our item to move. - $sql = 'UPDATE ' . TEAMPAGE_TABLE . ' - SET teampage_position = teampage_position' . (($move_up) ? ' + ' : ' - ') . $num_items . ' - WHERE teampage_position' . (($move_up) ? ' >= ' : ' <= ') . ($current_value - $delta) . ' - AND teampage_position' . (($move_up) ? ' < ' : ' > ') . $current_value . ' - AND NOT (teampage_id = ' . (int) $teampage_id . ' - OR teampage_parent = ' . (int) $teampage_id . ')'; - $this->db->sql_query($sql); - - $delta = (!$move_up && $data['teampage_parent'] == self::NO_PARENT) ? (abs($delta) - ($num_items - 1)) : abs($delta); - - // And now finally, when we moved some other items and built a gap, - // we can move the desired item to it. - $sql = 'UPDATE ' . TEAMPAGE_TABLE . ' - SET teampage_position = teampage_position ' . (($move_up) ? ' - ' : ' + ') . $delta . ' - WHERE teampage_id = ' . (int) $teampage_id . ' - OR teampage_parent = ' . (int) $teampage_id; - $this->db->sql_query($sql); - - $this->db->sql_transaction('commit'); - $this->cache->destroy('sql', TEAMPAGE_TABLE); - - return true; - } - - $this->db->sql_transaction('commit'); - } - - $this->cache->destroy('sql', TEAMPAGE_TABLE); - return false; - } - - /** - * Get group type language var - * - * @param int $group_type group_type from the groups-table - * @return string name of the language variable for the given group-type. - */ - static public function group_type_language($group_type) - { - switch ($group_type) - { - case GROUP_OPEN: - return 'GROUP_REQUEST'; - case GROUP_CLOSED: - return 'GROUP_CLOSED'; - case GROUP_HIDDEN: - return 'GROUP_HIDDEN'; - case GROUP_SPECIAL: - return 'GROUP_SPECIAL'; - case GROUP_FREE: - return 'GROUP_OPEN'; - } - } -} diff --git a/phpBB/includes/hook/finder.php b/phpBB/includes/hook/finder.php deleted file mode 100644 index 7b0412f733..0000000000 --- a/phpBB/includes/hook/finder.php +++ /dev/null @@ -1,84 +0,0 @@ -phpbb_root_path = $phpbb_root_path; - $this->cache = $cache; - $this->php_ext = $php_ext; - } - - /** - * Finds all hook files. - * - * @param bool $cache Whether the result should be cached - * @return array An array of paths to found hook files - */ - public function find($cache = true) - { - if (!defined('DEBUG') && $cache && $this->cache) - { - $hook_files = $this->cache->get('_hooks'); - if ($hook_files !== false) - { - return $hook_files; - } - } - - $hook_files = array(); - - // Now search for hooks... - $dh = @opendir($this->phpbb_root_path . 'includes/hooks/'); - - if ($dh) - { - while (($file = readdir($dh)) !== false) - { - if (strpos($file, 'hook_') === 0 && substr($file, -strlen('.' . $this->php_ext)) === '.' . $this->php_ext) - { - $hook_files[] = substr($file, 0, -(strlen($this->php_ext) + 1)); - } - } - closedir($dh); - } - - if ($cache && $this->cache) - { - $this->cache->put('_hooks', $hook_files); - } - - return $hook_files; - } -} diff --git a/phpBB/includes/json_response.php b/phpBB/includes/json_response.php deleted file mode 100644 index 5dd904da09..0000000000 --- a/phpBB/includes/json_response.php +++ /dev/null @@ -1,41 +0,0 @@ -config_name = $config_name; - $this->config = $config; - $this->db = $db; - } - - /** - * Tries to acquire the lock by updating - * the configuration variable in the database. - * - * As a lock may only be held by one process at a time, lock - * acquisition may fail if another process is holding the lock - * or if another process obtained the lock but never released it. - * Locks are forcibly released after a timeout of 1 hour. - * - * @return bool true if lock was acquired - * false otherwise - */ - public function acquire() - { - if ($this->locked) - { - return false; - } - - if (!isset($this->config[$this->config_name])) - { - $this->config->set($this->config_name, '0', false); - } - $lock_value = $this->config[$this->config_name]; - - // make sure lock cannot be acquired by multiple processes - if ($lock_value) - { - // if the other process is running more than an hour already we have to assume it - // aborted without cleaning the lock - $time = explode(' ', $lock_value); - $time = $time[0]; - - if ($time + 3600 >= time()) - { - return false; - } - } - - $this->unique_id = time() . ' ' . unique_id(); - - // try to update the config value, if it was already modified by another - // process we failed to acquire the lock. - $this->locked = $this->config->set_atomic($this->config_name, $lock_value, $this->unique_id, false); - - return $this->locked; - } - - /** - * Does this process own the lock? - * - * @return bool true if lock is owned - * false otherwise - */ - public function owns_lock() - { - return (bool) $this->locked; - } - - /** - * Releases the lock. - * - * The lock must have been previously obtained, that is, acquire() call - * was issued and returned true. - * - * Note: Attempting to release a lock that is already released, - * that is, calling release() multiple times, is harmless. - * - * @return null - */ - public function release() - { - if ($this->locked) - { - $this->config->set_atomic($this->config_name, $this->unique_id, '0', false); - $this->locked = false; - } - } -} diff --git a/phpBB/includes/lock/flock.php b/phpBB/includes/lock/flock.php deleted file mode 100644 index 17de0847c0..0000000000 --- a/phpBB/includes/lock/flock.php +++ /dev/null @@ -1,144 +0,0 @@ -path = $path; - $this->lock_fp = null; - } - - /** - * Tries to acquire the lock. - * - * If the lock is already held by another process, this call will block - * until the other process releases the lock. If a lock is acquired and - * is not released before script finishes but the process continues to - * live (apache/fastcgi) then subsequent processes trying to acquire - * the same lock will be blocked forever. - * - * If the lock is already held by the same process via another instance - * of this class, this call will block forever. - * - * If flock function is disabled in php or fails to work, lock - * acquisition will fail and false will be returned. - * - * @return bool true if lock was acquired - * false otherwise - */ - public function acquire() - { - if ($this->lock_fp) - { - return false; - } - - // For systems that can't have two processes opening - // one file for writing simultaneously - if (file_exists($this->path . '.lock')) - { - $mode = 'rb'; - } - else - { - $mode = 'wb'; - } - - $this->lock_fp = @fopen($this->path . '.lock', $mode); - - if ($mode == 'wb') - { - if (!$this->lock_fp) - { - // Two processes may attempt to create lock file at the same time. - // Have the losing process try opening the lock file again for reading - // on the assumption that the winning process created it - $mode = 'rb'; - $this->lock_fp = @fopen($this->path . '.lock', $mode); - } - else - { - // Only need to set mode when the lock file is written - @chmod($this->path . '.lock', 0666); - } - } - - if ($this->lock_fp) - { - @flock($this->lock_fp, LOCK_EX); - } - - return (bool) $this->lock_fp; - } - - /** - * Does this process own the lock? - * - * @return bool true if lock is owned - * false otherwise - */ - public function owns_lock() - { - return (bool) $this->lock_fp; - } - - /** - * Releases the lock. - * - * The lock must have been previously obtained, that is, acquire() call - * was issued and returned true. - * - * Note: Attempting to release a lock that is already released, - * that is, calling release() multiple times, is harmless. - * - * @return null - */ - public function release() - { - if ($this->lock_fp) - { - @flock($this->lock_fp, LOCK_UN); - fclose($this->lock_fp); - $this->lock_fp = null; - } - } -} diff --git a/phpBB/includes/log/interface.php b/phpBB/includes/log/interface.php deleted file mode 100644 index 3b459c9bdf..0000000000 --- a/phpBB/includes/log/interface.php +++ /dev/null @@ -1,106 +0,0 @@ -db = $db; - $this->user = $user; - $this->auth = $auth; - $this->dispatcher = $phpbb_dispatcher; - $this->phpbb_root_path = $phpbb_root_path; - $this->phpbb_admin_path = $this->phpbb_root_path . $relative_admin_path; - $this->php_ext = $php_ext; - $this->log_table = $log_table; - - /* - * IN_ADMIN is set after the session is created, - * so we need to take ADMIN_START into account as well, otherwise - * it will not work for the phpbb_log object we create in common.php - */ - $this->set_is_admin((defined('ADMIN_START') && ADMIN_START) || (defined('IN_ADMIN') && IN_ADMIN)); - $this->enable(); - } - - /** - * Set is_in_admin in order to return administrative user profile links - * in get_logs() - * - * @param bool $is_in_admin Are we called from within the acp? - * @return null - */ - public function set_is_admin($is_in_admin) - { - $this->is_in_admin = (bool) $is_in_admin; - } - - /** - * Returns the is_in_admin option - * - * @return bool - */ - public function get_is_admin() - { - return $this->is_in_admin; - } - - /** - * Set table name - * - * @param string $log_table Can overwrite the table to use for the logs - * @return null - */ - public function set_log_table($log_table) - { - $this->log_table = $log_table; - } - - /** - * This function returns the state of the log system. - * - * {@inheritDoc} - */ - public function is_enabled($type = '') - { - if ($type == '' || $type == 'all') - { - return !isset($this->disabled_types['all']); - } - return !isset($this->disabled_types[$type]) && !isset($this->disabled_types['all']); - } - - /** - * Disable log - * - * This function allows disabling the log system or parts of it, for this - * page call. When add_log is called and the type is disabled, - * the log will not be added to the database. - * - * {@inheritDoc} - */ - public function disable($type = '') - { - if (is_array($type)) - { - foreach ($type as $disable_type) - { - $this->disable($disable_type); - } - return; - } - - // Empty string is an equivalent for all types. - if ($type == '') - { - $type = 'all'; - } - $this->disabled_types[$type] = true; - } - - /** - * Enable log - * - * This function allows re-enabling the log system. - * - * {@inheritDoc} - */ - public function enable($type = '') - { - if (is_array($type)) - { - foreach ($type as $enable_type) - { - $this->enable($enable_type); - } - return; - } - - if ($type == '' || $type == 'all') - { - $this->disabled_types = array(); - return; - } - unset($this->disabled_types[$type]); - } - - /** - * Adds a log to the database - * - * {@inheritDoc} - */ - public function add($mode, $user_id, $log_ip, $log_operation, $log_time = false, $additional_data = array()) - { - if (!$this->is_enabled($mode)) - { - return false; - } - - if ($log_time == false) - { - $log_time = time(); - } - - $sql_ary = array( - 'user_id' => $user_id, - 'log_ip' => $log_ip, - 'log_time' => $log_time, - 'log_operation' => $log_operation, - ); - - switch ($mode) - { - case 'admin': - $sql_ary += array( - 'log_type' => LOG_ADMIN, - 'log_data' => (!empty($additional_data)) ? serialize($additional_data) : '', - ); - break; - - case 'mod': - $forum_id = (int) $additional_data['forum_id']; - unset($additional_data['forum_id']); - $topic_id = (int) $additional_data['topic_id']; - unset($additional_data['topic_id']); - $sql_ary += array( - 'log_type' => LOG_MOD, - 'forum_id' => $forum_id, - 'topic_id' => $topic_id, - 'log_data' => (!empty($additional_data)) ? serialize($additional_data) : '', - ); - break; - - case 'user': - $reportee_id = (int) $additional_data['reportee_id']; - unset($additional_data['reportee_id']); - - $sql_ary += array( - 'log_type' => LOG_USERS, - 'reportee_id' => $reportee_id, - 'log_data' => (!empty($additional_data)) ? serialize($additional_data) : '', - ); - break; - - case 'critical': - $sql_ary += array( - 'log_type' => LOG_CRITICAL, - 'log_data' => (!empty($additional_data)) ? serialize($additional_data) : '', - ); - break; - } - - /** - * Allows to modify log data before we add it to the database - * - * NOTE: if sql_ary does not contain a log_type value, the entry will - * not be stored in the database. So ensure to set it, if needed. - * - * @event core.add_log - * @var string mode Mode of the entry we log - * @var int user_id ID of the user who triggered the log - * @var string log_ip IP of the user who triggered the log - * @var string log_operation Language key of the log operation - * @var int log_time Timestamp, when the log was added - * @var array additional_data Array with additional log data - * @var array sql_ary Array with log data we insert into the - * database. If sql_ary[log_type] is not set, - * we won't add the entry to the database. - * @since 3.1-A1 - */ - $vars = array('mode', 'user_id', 'log_ip', 'log_operation', 'log_time', 'additional_data', 'sql_ary'); - extract($this->dispatcher->trigger_event('core.add_log', $vars)); - - // We didn't find a log_type, so we don't save it in the database. - if (!isset($sql_ary['log_type'])) - { - return false; - } - - $this->db->sql_query('INSERT INTO ' . $this->log_table . ' ' . $this->db->sql_build_array('INSERT', $sql_ary)); - - return $this->db->sql_nextid(); - } - - /** - * Grab the logs from the database - * - * {@inheritDoc} - */ - public function get_logs($mode, $count_logs = true, $limit = 0, $offset = 0, $forum_id = 0, $topic_id = 0, $user_id = 0, $log_time = 0, $sort_by = 'l.log_time DESC', $keywords = '') - { - $this->entry_count = 0; - $this->last_page_offset = $offset; - - $topic_id_list = $reportee_id_list = array(); - - $profile_url = ($this->get_is_admin() && $this->phpbb_admin_path) ? append_sid("{$this->phpbb_admin_path}index.{$this->php_ext}", 'i=users&mode=overview') : append_sid("{$this->phpbb_root_path}memberlist.{$this->php_ext}", 'mode=viewprofile'); - - switch ($mode) - { - case 'admin': - $log_type = LOG_ADMIN; - $sql_additional = ''; - break; - - case 'mod': - $log_type = LOG_MOD; - $sql_additional = ''; - - if ($topic_id) - { - $sql_additional = 'AND l.topic_id = ' . (int) $topic_id; - } - else if (is_array($forum_id)) - { - $sql_additional = 'AND ' . $this->db->sql_in_set('l.forum_id', array_map('intval', $forum_id)); - } - else if ($forum_id) - { - $sql_additional = 'AND l.forum_id = ' . (int) $forum_id; - } - break; - - case 'user': - $log_type = LOG_USERS; - $sql_additional = 'AND l.reportee_id = ' . (int) $user_id; - break; - - case 'users': - $log_type = LOG_USERS; - $sql_additional = ''; - break; - - case 'critical': - $log_type = LOG_CRITICAL; - $sql_additional = ''; - break; - - default: - $log_type = false; - $sql_additional = ''; - } - - /** - * Overwrite log type and limitations before we count and get the logs - * - * NOTE: if log_type is false, no entries will be returned. - * - * @event core.get_logs_modify_type - * @var string mode Mode of the entries we display - * @var bool count_logs Do we count all matching entries? - * @var int limit Limit the number of entries - * @var int offset Offset when fetching the entries - * @var mixed forum_id Limit entries to the forum_id, - * can also be an array of forum_ids - * @var int topic_id Limit entries to the topic_id - * @var int user_id Limit entries to the user_id - * @var int log_time Limit maximum age of log entries - * @var string sort_by SQL order option - * @var string keywords Will only return entries that have the - * keywords in log_operation or log_data - * @var string profile_url URL to the users profile - * @var int log_type Limit logs to a certain type. If log_type - * is false, no entries will be returned. - * @var string sql_additional Additional conditions for the entries, - * e.g.: 'AND l.forum_id = 1' - * @since 3.1-A1 - */ - $vars = array('mode', 'count_logs', 'limit', 'offset', 'forum_id', 'topic_id', 'user_id', 'log_time', 'sort_by', 'keywords', 'profile_url', 'log_type', 'sql_additional'); - extract($this->dispatcher->trigger_event('core.get_logs_modify_type', $vars)); - - if ($log_type === false) - { - $this->last_page_offset = 0; - return array(); - } - - $sql_keywords = ''; - if (!empty($keywords)) - { - // Get the SQL condition for our keywords - $sql_keywords = $this->generate_sql_keyword($keywords); - } - - if ($count_logs) - { - $sql = 'SELECT COUNT(l.log_id) AS total_entries - FROM ' . LOG_TABLE . ' l, ' . USERS_TABLE . ' u - WHERE l.log_type = ' . (int) $log_type . ' - AND l.user_id = u.user_id - AND l.log_time >= ' . (int) $log_time . " - $sql_keywords - $sql_additional"; - $result = $this->db->sql_query($sql); - $this->entry_count = (int) $this->db->sql_fetchfield('total_entries'); - $this->db->sql_freeresult($result); - - if ($this->entry_count == 0) - { - // Save the queries, because there are no logs to display - $this->last_page_offset = 0; - return array(); - } - - // Return the user to the last page that is valid - while ($this->last_page_offset >= $this->entry_count) - { - $this->last_page_offset = max(0, $this->last_page_offset - $limit); - } - } - - $sql = 'SELECT l.*, u.username, u.username_clean, u.user_colour - FROM ' . LOG_TABLE . ' l, ' . USERS_TABLE . ' u - WHERE l.log_type = ' . (int) $log_type . ' - AND u.user_id = l.user_id - ' . (($log_time) ? 'AND l.log_time >= ' . (int) $log_time : '') . " - $sql_keywords - $sql_additional - ORDER BY $sort_by"; - $result = $this->db->sql_query_limit($sql, $limit, $this->last_page_offset); - - $i = 0; - $log = array(); - while ($row = $this->db->sql_fetchrow($result)) - { - $row['forum_id'] = (int) $row['forum_id']; - if ($row['topic_id']) - { - $topic_id_list[] = (int) $row['topic_id']; - } - - if ($row['reportee_id']) - { - $reportee_id_list[] = (int) $row['reportee_id']; - } - - $log_entry_data = array( - 'id' => (int) $row['log_id'], - - 'reportee_id' => (int) $row['reportee_id'], - 'reportee_username' => '', - 'reportee_username_full'=> '', - - 'user_id' => (int) $row['user_id'], - 'username' => $row['username'], - 'username_full' => get_username_string('full', $row['user_id'], $row['username'], $row['user_colour'], false, $profile_url), - - 'ip' => $row['log_ip'], - 'time' => (int) $row['log_time'], - 'forum_id' => (int) $row['forum_id'], - 'topic_id' => (int) $row['topic_id'], - - 'viewforum' => ($row['forum_id'] && $this->auth->acl_get('f_read', $row['forum_id'])) ? append_sid("{$this->phpbb_root_path}viewforum.{$this->php_ext}", 'f=' . $row['forum_id']) : false, - 'action' => (isset($this->user->lang[$row['log_operation']])) ? $this->user->lang[$row['log_operation']] : '{' . ucfirst(str_replace('_', ' ', $row['log_operation'])) . '}', - ); - - /** - * Modify the entry's data before it is returned - * - * @event core.get_logs_modify_entry_data - * @var array row Entry data from the database - * @var array log_entry_data Entry's data which is returned - * @since 3.1-A1 - */ - $vars = array('row', 'log_entry_data'); - extract($this->dispatcher->trigger_event('core.get_logs_modify_entry_data', $vars)); - - $log[$i] = $log_entry_data; - - if (!empty($row['log_data'])) - { - $log_data_ary = unserialize($row['log_data']); - $log_data_ary = ($log_data_ary !== false) ? $log_data_ary : array(); - - if (isset($this->user->lang[$row['log_operation']])) - { - // Check if there are more occurrences of % than - // arguments, if there are we fill out the arguments - // array. It doesn't matter if we add more arguments than - // placeholders. - if ((substr_count($log[$i]['action'], '%') - sizeof($log_data_ary)) > 0) - { - $log_data_ary = array_merge($log_data_ary, array_fill(0, substr_count($log[$i]['action'], '%') - sizeof($log_data_ary), '')); - } - - $log[$i]['action'] = vsprintf($log[$i]['action'], $log_data_ary); - - // If within the admin panel we do not censor text out - if ($this->get_is_admin()) - { - $log[$i]['action'] = bbcode_nl2br($log[$i]['action']); - } - else - { - $log[$i]['action'] = bbcode_nl2br(censor_text($log[$i]['action'])); - } - } - else if (!empty($log_data_ary)) - { - $log[$i]['action'] .= '
' . implode('', $log_data_ary); - } - - /* Apply make_clickable... has to be seen if it is for good. :/ - // Seems to be not for the moment, reconsider later... - $log[$i]['action'] = make_clickable($log[$i]['action']); - */ - } - - $i++; - } - $this->db->sql_freeresult($result); - - /** - * Get some additional data after we got all log entries - * - * @event core.get_logs_get_additional_data - * @var array log Array with all our log entries - * @var array topic_id_list Array of topic ids, for which we - * get the permission data - * @var array reportee_id_list Array of additional user IDs we - * get the username strings for - * @since 3.1-A1 - */ - $vars = array('log', 'topic_id_list', 'reportee_id_list'); - extract($this->dispatcher->trigger_event('core.get_logs_get_additional_data', $vars)); - - if (sizeof($topic_id_list)) - { - $topic_auth = $this->get_topic_auth($topic_id_list); - - foreach ($log as $key => $row) - { - $log[$key]['viewtopic'] = (isset($topic_auth['f_read'][$row['topic_id']])) ? append_sid("{$this->phpbb_root_path}viewtopic.{$this->php_ext}", 'f=' . $topic_auth['f_read'][$row['topic_id']] . '&t=' . $row['topic_id']) : false; - $log[$key]['viewlogs'] = (isset($topic_auth['m_'][$row['topic_id']])) ? append_sid("{$this->phpbb_root_path}mcp.{$this->php_ext}", 'i=logs&mode=topic_logs&t=' . $row['topic_id'], true, $this->user->session_id) : false; - } - } - - if (sizeof($reportee_id_list)) - { - $reportee_data_list = $this->get_reportee_data($reportee_id_list); - - foreach ($log as $key => $row) - { - if (!isset($reportee_data_list[$row['reportee_id']])) - { - continue; - } - - $log[$key]['reportee_username'] = $reportee_data_list[$row['reportee_id']]['username']; - $log[$key]['reportee_username_full'] = get_username_string('full', $row['reportee_id'], $reportee_data_list[$row['reportee_id']]['username'], $reportee_data_list[$row['reportee_id']]['user_colour'], false, $profile_url); - } - } - - return $log; - } - - /** - * Generates a sql condition for the specified keywords - * - * @param string $keywords The keywords the user specified to search for - * - * @return string Returns the SQL condition searching for the keywords - */ - protected function generate_sql_keyword($keywords) - { - // Use no preg_quote for $keywords because this would lead to sole - // backslashes being added. We also use an OR connection here for - // spaces and the | string. Currently, regex is not supported for - // searching (but may come later). - $keywords = preg_split('#[\s|]+#u', utf8_strtolower($keywords), 0, PREG_SPLIT_NO_EMPTY); - $sql_keywords = ''; - - if (!empty($keywords)) - { - $keywords_pattern = array(); - - // Build pattern and keywords... - for ($i = 0, $num_keywords = sizeof($keywords); $i < $num_keywords; $i++) - { - $keywords_pattern[] = preg_quote($keywords[$i], '#'); - $keywords[$i] = $this->db->sql_like_expression($this->db->any_char . $keywords[$i] . $this->db->any_char); - } - - $keywords_pattern = '#' . implode('|', $keywords_pattern) . '#ui'; - - $operations = array(); - foreach ($this->user->lang as $key => $value) - { - if (substr($key, 0, 4) == 'LOG_' && preg_match($keywords_pattern, $value)) - { - $operations[] = $key; - } - } - - $sql_keywords = 'AND ('; - if (!empty($operations)) - { - $sql_keywords .= $this->db->sql_in_set('l.log_operation', $operations) . ' OR '; - } - $sql_lower = $this->db->sql_lower_text('l.log_data'); - $sql_keywords .= " $sql_lower " . implode(" OR $sql_lower ", $keywords) . ')'; - } - - return $sql_keywords; - } - - /** - * Determine whether the user is allowed to read and/or moderate the forum of the topic - * - * @param array $topic_ids Array with the topic ids - * - * @return array Returns an array with two keys 'm_' and 'read_f' which are also an array of topic_id => forum_id sets when the permissions are given. Sample: - * array( - * 'permission' => array( - * topic_id => forum_id - * ), - * ), - */ - protected function get_topic_auth(array $topic_ids) - { - $forum_auth = array('f_read' => array(), 'm_' => array()); - $topic_ids = array_unique($topic_ids); - - $sql = 'SELECT topic_id, forum_id - FROM ' . TOPICS_TABLE . ' - WHERE ' . $this->db->sql_in_set('topic_id', array_map('intval', $topic_ids)); - $result = $this->db->sql_query($sql); - - while ($row = $this->db->sql_fetchrow($result)) - { - $row['topic_id'] = (int) $row['topic_id']; - $row['forum_id'] = (int) $row['forum_id']; - - if ($this->auth->acl_get('f_read', $row['forum_id'])) - { - $forum_auth['f_read'][$row['topic_id']] = $row['forum_id']; - } - - if ($this->auth->acl_gets('a_', 'm_', $row['forum_id'])) - { - $forum_auth['m_'][$row['topic_id']] = $row['forum_id']; - } - } - $this->db->sql_freeresult($result); - - return $forum_auth; - } - - /** - * Get the data for all reportee from the database - * - * @param array $reportee_ids Array with the user ids of the reportees - * - * @return array Returns an array with the reportee data - */ - protected function get_reportee_data(array $reportee_ids) - { - $reportee_ids = array_unique($reportee_ids); - $reportee_data_list = array(); - - $sql = 'SELECT user_id, username, user_colour - FROM ' . USERS_TABLE . ' - WHERE ' . $this->db->sql_in_set('user_id', $reportee_ids); - $result = $this->db->sql_query($sql); - - while ($row = $this->db->sql_fetchrow($result)) - { - $reportee_data_list[$row['user_id']] = $row; - } - $this->db->sql_freeresult($result); - - return $reportee_data_list; - } - - /** - * Get total log count - * - * {@inheritDoc} - */ - public function get_log_count() - { - return ($this->entry_count) ? $this->entry_count : 0; - } - - /** - * Get offset of the last valid log page - * - * {@inheritDoc} - */ - public function get_valid_offset() - { - return ($this->last_page_offset) ? $this->last_page_offset : 0; - } -} diff --git a/phpBB/includes/notification/exception.php b/phpBB/includes/notification/exception.php deleted file mode 100644 index a52d6fdc57..0000000000 --- a/phpBB/includes/notification/exception.php +++ /dev/null @@ -1,29 +0,0 @@ -getMessage(); - } -} diff --git a/phpBB/includes/notification/manager.php b/phpBB/includes/notification/manager.php deleted file mode 100644 index 97833710c0..0000000000 --- a/phpBB/includes/notification/manager.php +++ /dev/null @@ -1,910 +0,0 @@ -notification_types = $notification_types; - $this->notification_methods = $notification_methods; - $this->phpbb_container = $phpbb_container; - - $this->user_loader = $user_loader; - $this->db = $db; - $this->cache = $cache; - $this->user = $user; - - $this->phpbb_root_path = $phpbb_root_path; - $this->php_ext = $php_ext; - - $this->notification_types_table = $notification_types_table; - $this->notifications_table = $notifications_table; - $this->user_notifications_table = $user_notifications_table; - } - - /** - * Load the user's notifications - * - * @param array $options Optional options to control what notifications are loaded - * notification_id Notification id to load (or array of notification ids) - * user_id User id to load notifications for (Default: $user->data['user_id']) - * order_by Order by (Default: notification_time) - * order_dir Order direction (Default: DESC) - * limit Number of notifications to load (Default: 5) - * start Notifications offset (Default: 0) - * all_unread Load all unread notifications? If set to true, count_unread is set to true (Default: false) - * count_unread Count all unread notifications? (Default: false) - * count_total Count all notifications? (Default: false) - * @return array Array of information based on the request with keys: - * 'notifications' array of notification type objects - * 'unread_count' number of unread notifications the user has if count_unread is true in the options - * 'total_count' number of notifications the user has if count_total is true in the options - */ - public function load_notifications(array $options = array()) - { - // Merge default options - $options = array_merge(array( - 'notification_id' => false, - 'user_id' => $this->user->data['user_id'], - 'order_by' => 'notification_time', - 'order_dir' => 'DESC', - 'limit' => 0, - 'start' => 0, - 'all_unread' => false, - 'count_unread' => false, - 'count_total' => false, - ), $options); - - // If all_unread, count_unread must be true - $options['count_unread'] = ($options['all_unread']) ? true : $options['count_unread']; - - // Anonymous users and bots never receive notifications - if ($options['user_id'] == $this->user->data['user_id'] && ($this->user->data['user_id'] == ANONYMOUS || $this->user->data['user_type'] == USER_IGNORE)) - { - return array( - 'notifications' => array(), - 'unread_count' => 0, - 'total_count' => 0, - ); - } - - $notifications = $user_ids = array(); - $load_special = array(); - $total_count = $unread_count = 0; - - if ($options['count_unread']) - { - // Get the total number of unread notifications - $sql = 'SELECT COUNT(n.notification_id) AS unread_count - FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . ' nt - WHERE n.user_id = ' . (int) $options['user_id'] . ' - AND n.notification_read = 0 - AND nt.notification_type_id = n.notification_type_id - AND nt.notification_type_enabled = 1'; - $result = $this->db->sql_query($sql); - $unread_count = (int) $this->db->sql_fetchfield('unread_count', $result); - $this->db->sql_freeresult($result); - } - - if ($options['count_total']) - { - // Get the total number of notifications - $sql = 'SELECT COUNT(n.notification_id) AS total_count - FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . ' nt - WHERE n.user_id = ' . (int) $options['user_id'] . ' - AND nt.notification_type_id = n.notification_type_id - AND nt.notification_type_enabled = 1'; - $result = $this->db->sql_query($sql); - $total_count = (int) $this->db->sql_fetchfield('total_count', $result); - $this->db->sql_freeresult($result); - } - - if (!$options['count_total'] || $total_count) - { - $rowset = array(); - - // Get the main notifications - $sql = 'SELECT n.*, nt.notification_type_name - FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . ' nt - WHERE n.user_id = ' . (int) $options['user_id'] . - (($options['notification_id']) ? ((is_array($options['notification_id'])) ? ' AND ' . $this->db->sql_in_set('n.notification_id', $options['notification_id']) : ' AND n.notification_id = ' . (int) $options['notification_id']) : '') . ' - AND nt.notification_type_id = n.notification_type_id - AND nt.notification_type_enabled = 1 - ORDER BY n.' . $this->db->sql_escape($options['order_by']) . ' ' . $this->db->sql_escape($options['order_dir']); - $result = $this->db->sql_query_limit($sql, $options['limit'], $options['start']); - - while ($row = $this->db->sql_fetchrow($result)) - { - $rowset[$row['notification_id']] = $row; - } - $this->db->sql_freeresult($result); - - // Get all unread notifications - if ($unread_count && $options['all_unread'] && !empty($rowset)) - { - $sql = 'SELECT n.*, nt.notification_type_name - FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . ' nt - WHERE n.user_id = ' . (int) $options['user_id'] . ' - AND n.notification_read = 0 - AND ' . $this->db->sql_in_set('n.notification_id', array_keys($rowset), true) . ' - AND nt.notification_type_id = n.notification_type_id - AND nt.notification_type_enabled = 1 - ORDER BY n.' . $this->db->sql_escape($options['order_by']) . ' ' . $this->db->sql_escape($options['order_dir']); - $result = $this->db->sql_query_limit($sql, $options['limit'], $options['start']); - - while ($row = $this->db->sql_fetchrow($result)) - { - $rowset[$row['notification_id']] = $row; - } - $this->db->sql_freeresult($result); - } - - foreach ($rowset as $row) - { - $notification = $this->get_item_type_class($row['notification_type_name'], $row); - - // Array of user_ids to query all at once - $user_ids = array_merge($user_ids, $notification->users_to_query()); - - // Some notification types also require querying additional tables themselves - if (!isset($load_special[$row['notification_type_name']])) - { - $load_special[$row['notification_type_name']] = array(); - } - $load_special[$row['notification_type_name']] = array_merge($load_special[$row['notification_type_name']], $notification->get_load_special()); - - $notifications[$row['notification_id']] = $notification; - } - - $this->user_loader->load_users($user_ids); - - // Allow each type to load its own special items - foreach ($load_special as $item_type => $data) - { - $item_class = $this->get_item_type_class($item_type); - - $item_class->load_special($data, $notifications); - } - } - - return array( - 'notifications' => $notifications, - 'unread_count' => $unread_count, - 'total_count' => $total_count, - ); - } - - /** - * Mark notifications read - * - * @param bool|string|array $notification_type_name Type identifier or array of item types (only acceptable if the $data is identical for the specified types). False to mark read for all item types - * @param bool|int|array $item_id Item id or array of item ids. False to mark read for all item ids - * @param bool|int|array $user_id User id or array of user ids. False to mark read for all user ids - * @param bool|int $time Time at which to mark all notifications prior to as read. False to mark all as read. (Default: False) - */ - public function mark_notifications_read($notification_type_name, $item_id, $user_id, $time = false) - { - $time = ($time !== false) ? $time : time(); - - $sql = 'UPDATE ' . $this->notifications_table . " - SET notification_read = 1 - WHERE notification_time <= " . (int) $time . - (($notification_type_name !== false) ? ' AND ' . - (is_array($notification_type_name) ? $this->db->sql_in_set('notification_type_id', $this->get_notification_type_ids($notification_type_name)) : 'notification_type_id = ' . $this->get_notification_type_id($notification_type_name)) - : '') . - (($user_id !== false) ? ' AND ' . (is_array($user_id) ? $this->db->sql_in_set('user_id', $user_id) : 'user_id = ' . (int) $user_id) : '') . - (($item_id !== false) ? ' AND ' . (is_array($item_id) ? $this->db->sql_in_set('item_id', $item_id) : 'item_id = ' . (int) $item_id) : ''); - $this->db->sql_query($sql); - } - - /** - * Mark notifications read from a parent identifier - * - * @param string|array $notification_type_name Type identifier or array of item types (only acceptable if the $data is identical for the specified types) - * @param bool|int|array $item_parent_id Item parent id or array of item parent ids. False to mark read for all item parent ids - * @param bool|int|array $user_id User id or array of user ids. False to mark read for all user ids - * @param bool|int $time Time at which to mark all notifications prior to as read. False to mark all as read. (Default: False) - */ - public function mark_notifications_read_by_parent($notification_type_name, $item_parent_id, $user_id, $time = false) - { - $time = ($time !== false) ? $time : time(); - - $sql = 'UPDATE ' . $this->notifications_table . " - SET notification_read = 1 - WHERE notification_time <= " . (int) $time . - (($notification_type_name !== false) ? ' AND ' . - (is_array($notification_type_name) ? $this->db->sql_in_set('notification_type_id', $this->get_notification_type_ids($notification_type_name)) : 'notification_type_id = ' . $this->get_notification_type_id($notification_type_name)) - : '') . - (($item_parent_id !== false) ? ' AND ' . (is_array($item_parent_id) ? $this->db->sql_in_set('item_parent_id', $item_parent_id) : 'item_parent_id = ' . (int) $item_parent_id) : '') . - (($user_id !== false) ? ' AND ' . (is_array($user_id) ? $this->db->sql_in_set('user_id', $user_id) : 'user_id = ' . (int) $user_id) : ''); - $this->db->sql_query($sql); - } - - /** - * Mark notifications read - * - * @param int|array $notification_id Notification id or array of notification ids. - * @param bool|int $time Time at which to mark all notifications prior to as read. False to mark all as read. (Default: False) - */ - public function mark_notifications_read_by_id($notification_id, $time = false) - { - $time = ($time !== false) ? $time : time(); - - $sql = 'UPDATE ' . $this->notifications_table . " - SET notification_read = 1 - WHERE notification_time <= " . (int) $time . ' - AND ' . ((is_array($notification_id)) ? $this->db->sql_in_set('notification_id', $notification_id) : 'notification_id = ' . (int) $notification_id); - $this->db->sql_query($sql); - } - - /** - * Add a notification - * - * @param string|array $notification_type_name Type identifier or array of item types (only acceptable if the $data is identical for the specified types) - * Note: If you send an array of types, any user who could receive multiple notifications from this single item will only receive - * a single notification. If they MUST receive multiple notifications, call this function multiple times instead of sending an array - * @param array $data Data specific for this type that will be inserted - * @param array $options Optional options to control what notifications are loaded - * ignore_users array of data to specify which users should not receive certain types of notifications - * @return array Information about what users were notified and how they were notified - */ - public function add_notifications($notification_type_name, $data, array $options = array()) - { - $options = array_merge(array( - 'ignore_users' => array(), - ), $options); - - if (is_array($notification_type_name)) - { - $notified_users = array(); - $temp_options = $options; - - foreach ($notification_type_name as $type) - { - $temp_options['ignore_users'] = $options['ignore_users'] + $notified_users; - $notified_users += $this->add_notifications($type, $data, $temp_options); - } - - return $notified_users; - } - - $item_id = $this->get_item_type_class($notification_type_name)->get_item_id($data); - - // find out which users want to receive this type of notification - $notify_users = $this->get_item_type_class($notification_type_name)->find_users_for_notification($data, $options); - - $this->add_notifications_for_users($notification_type_name, $data, $notify_users); - - return $notify_users; - } - - /** - * Add a notification for specific users - * - * @param string|array $notification_type_name Type identifier or array of item types (only acceptable if the $data is identical for the specified types) - * @param array $data Data specific for this type that will be inserted - * @param array $notify_users User list to notify - */ - public function add_notifications_for_users($notification_type_name, $data, $notify_users) - { - if (is_array($notification_type_name)) - { - foreach ($notification_type_name as $type) - { - $this->add_notifications_for_users($type, $data, $notify_users); - } - - return; - } - - $notification_type_id = $this->get_notification_type_id($notification_type_name); - - $item_id = $this->get_item_type_class($notification_type_name)->get_item_id($data); - - $user_ids = array(); - $notification_objects = $notification_methods = array(); - - // Never send notifications to the anonymous user! - unset($notify_users[ANONYMOUS]); - - // Make sure not to send new notifications to users who've already been notified about this item - // This may happen when an item was added, but now new users are able to see the item - $sql = 'SELECT n.user_id - FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . ' nt - WHERE n.notification_type_id = ' . (int) $notification_type_id . ' - AND n.item_id = ' . (int) $item_id . ' - AND nt.notification_type_id = n.notification_type_id - AND nt.notification_type_enabled = 1'; - $result = $this->db->sql_query($sql); - while ($row = $this->db->sql_fetchrow($result)) - { - unset($notify_users[$row['user_id']]); - } - $this->db->sql_freeresult($result); - - if (!sizeof($notify_users)) - { - return; - } - - // Allow notifications to perform actions before creating the insert array (such as run a query to cache some data needed for all notifications) - $notification = $this->get_item_type_class($notification_type_name); - $pre_create_data = $notification->pre_create_insert_array($data, $notify_users); - unset($notification); - - $insert_buffer = new phpbb_db_sql_insert_buffer($this->db, $this->notifications_table); - - // Go through each user so we can insert a row in the DB and then notify them by their desired means - foreach ($notify_users as $user => $methods) - { - $notification = $this->get_item_type_class($notification_type_name); - - $notification->user_id = (int) $user; - - // Insert notification row using buffer. - $insert_buffer->insert($notification->create_insert_array($data, $pre_create_data)); - - // Users are needed to send notifications - $user_ids = array_merge($user_ids, $notification->users_to_query()); - - foreach ($methods as $method) - { - // setup the notification methods and add the notification to the queue - if ($method) // blank means we just insert it as a notification, but do not notify them by any other means - { - if (!isset($notification_methods[$method])) - { - $notification_methods[$method] = $this->get_method_class($method); - } - - $notification_methods[$method]->add_to_queue($notification); - } - } - } - - $insert_buffer->flush(); - - // We need to load all of the users to send notifications - $this->user_loader->load_users($user_ids); - - // run the queue for each method to send notifications - foreach ($notification_methods as $method) - { - $method->notify(); - } - } - - /** - * Update a notification - * - * @param string|array $notification_type_name Type identifier or array of item types (only acceptable if the $data is identical for the specified types) - * @param array $data Data specific for this type that will be updated - */ - public function update_notifications($notification_type_name, $data) - { - if (is_array($notification_type_name)) - { - foreach ($notification_type_name as $type) - { - $this->update_notifications($type, $data); - } - - return; - } - - $notification = $this->get_item_type_class($notification_type_name); - - // Allow the notifications class to over-ride the update_notifications functionality - if (method_exists($notification, 'update_notifications')) - { - // Return False to over-ride the rest of the update - if ($notification->update_notifications($data) === false) - { - return; - } - } - - $notification_type_id = $this->get_notification_type_id($notification_type_name); - $item_id = $notification->get_item_id($data); - $update_array = $notification->create_update_array($data); - - $sql = 'UPDATE ' . $this->notifications_table . ' - SET ' . $this->db->sql_build_array('UPDATE', $update_array) . ' - WHERE notification_type_id = ' . (int) $notification_type_id . ' - AND item_id = ' . (int) $item_id; - $this->db->sql_query($sql); - } - - /** - * Delete a notification - * - * @param string|array $notification_type_name Type identifier or array of item types (only acceptable if the $item_id is identical for the specified types) - * @param int|array $item_id Identifier within the type (or array of ids) - * @param array $data Data specific for this type that will be updated - */ - public function delete_notifications($notification_type_name, $item_id) - { - if (is_array($notification_type_name)) - { - foreach ($notification_type_name as $type) - { - $this->delete_notifications($type, $item_id); - } - - return; - } - - $notification_type_id = $this->get_notification_type_id($notification_type_name); - - $sql = 'DELETE FROM ' . $this->notifications_table . ' - WHERE notification_type_id = ' . (int) $notification_type_id . ' - AND ' . (is_array($item_id) ? $this->db->sql_in_set('item_id', $item_id) : 'item_id = ' . (int) $item_id); - $this->db->sql_query($sql); - } - - /** - * Get all of the subscription types - * - * @return array Array of item types - */ - public function get_subscription_types() - { - $subscription_types = array(); - - foreach ($this->notification_types as $type_name => $data) - { - $type = $this->get_item_type_class($type_name); - - if ($type instanceof phpbb_notification_type_interface && $type->is_available()) - { - $options = array_merge(array( - 'id' => $type->get_type(), - 'lang' => 'NOTIFICATION_TYPE_' . strtoupper($type->get_type()), - 'group' => 'NOTIFICATION_GROUP_MISCELLANEOUS', - ), (($type::$notification_option !== false) ? $type::$notification_option : array())); - - $subscription_types[$options['group']][$options['id']] = $options; - } - } - - // Move Miscellaneous to the very last section - if (isset($subscription_types['NOTIFICATION_GROUP_MISCELLANEOUS'])) - { - $miscellaneous = $subscription_types['NOTIFICATION_GROUP_MISCELLANEOUS']; - unset($subscription_types['NOTIFICATION_GROUP_MISCELLANEOUS']); - $subscription_types['NOTIFICATION_GROUP_MISCELLANEOUS'] = $miscellaneous; - } - - return $subscription_types; - } - - /** - * Get all of the subscription methods - * - * @return array Array of methods - */ - public function get_subscription_methods() - { - $subscription_methods = array(); - - foreach ($this->notification_methods as $method_name => $data) - { - $method = $this->get_method_class($method_name); - - if ($method instanceof phpbb_notification_method_interface && $method->is_available()) - { - $subscription_methods[$method_name] = array( - 'id' => $method->get_type(), - 'lang' => 'NOTIFICATION_METHOD_' . strtoupper($method->get_type()), - ); - } - } - - return $subscription_methods; - } - - /** - * Get global subscriptions (item_id = 0) - * - * @param bool|int $user_id The user_id to add the subscription for (bool false for current user) - * - * @return array Subscriptions - */ - public function get_global_subscriptions($user_id = false) - { - $user_id = ($user_id === false) ? $this->user->data['user_id'] : $user_id; - - $subscriptions = array(); - - foreach ($this->get_subscription_types() as $group_name => $types) - { - foreach ($types as $id => $type) - { - $sql = 'SELECT method, notify - FROM ' . $this->user_notifications_table . ' - WHERE user_id = ' . (int) $user_id . " - AND item_type = '" . $this->db->sql_escape($id) . "' - AND item_id = 0"; - $result = $this->db->sql_query($sql); - - $row = $this->db->sql_fetchrow($result); - if (!$row) - { - // No rows at all, default to '' - $subscriptions[$id] = array(''); - } - else - { - do - { - if (!$row['notify']) - { - continue; - } - - if (!isset($subscriptions[$id])) - { - $subscriptions[$id] = array(); - } - - $subscriptions[$id][] = $row['method']; - } - while ($row = $this->db->sql_fetchrow($result)); - } - - $this->db->sql_freeresult($result); - } - } - - return $subscriptions; - } - - /** - * Add a subscription - * - * @param string $item_type Type identifier of the subscription - * @param int $item_id The id of the item - * @param string $method The method of the notification e.g. '', 'email', or 'jabber' - * @param bool|int $user_id The user_id to add the subscription for (bool false for current user) - */ - public function add_subscription($item_type, $item_id = 0, $method = '', $user_id = false) - { - if ($method !== '') - { - // Make sure to subscribe them to the base subscription - $this->add_subscription($item_type, $item_id, '', $user_id); - } - - $user_id = ($user_id === false) ? $this->user->data['user_id'] : $user_id; - - $sql = 'SELECT notify - FROM ' . $this->user_notifications_table . " - WHERE item_type = '" . $this->db->sql_escape($item_type) . "' - AND item_id = " . (int) $item_id . ' - AND user_id = ' .(int) $user_id . " - AND method = '" . $this->db->sql_escape($method) . "'"; - $this->db->sql_query($sql); - $current = $this->db->sql_fetchfield('notify'); - $this->db->sql_freeresult(); - - if ($current === false) - { - $sql = 'INSERT INTO ' . $this->user_notifications_table . ' ' . - $this->db->sql_build_array('INSERT', array( - 'item_type' => $item_type, - 'item_id' => (int) $item_id, - 'user_id' => (int) $user_id, - 'method' => $method, - 'notify' => 1, - )); - $this->db->sql_query($sql); - } - else if (!$current) - { - $sql = 'UPDATE ' . $this->user_notifications_table . " - SET notify = 1 - WHERE item_type = '" . $this->db->sql_escape($item_type) . "' - AND item_id = " . (int) $item_id . ' - AND user_id = ' .(int) $user_id . " - AND method = '" . $this->db->sql_escape($method) . "'"; - $this->db->sql_query($sql); - } - } - - /** - * Delete a subscription - * - * @param string $item_type Type identifier of the subscription - * @param int $item_id The id of the item - * @param string $method The method of the notification e.g. '', 'email', or 'jabber' - * @param bool|int $user_id The user_id to add the subscription for (bool false for current user) - */ - public function delete_subscription($item_type, $item_id = 0, $method = '', $user_id = false) - { - $user_id = ($user_id === false) ? $this->user->data['user_id'] : $user_id; - - // If no method, make sure that no other notification methods for this item are selected before deleting - if ($method === '') - { - $sql = 'SELECT COUNT(*) as num_notifications - FROM ' . $this->user_notifications_table . " - WHERE item_type = '" . $this->db->sql_escape($item_type) . "' - AND item_id = " . (int) $item_id . ' - AND user_id = ' .(int) $user_id . " - AND method <> '' - AND notify = 1"; - $this->db->sql_query($sql); - $num_notifications = $this->db->sql_fetchfield('num_notifications'); - $this->db->sql_freeresult(); - - if ($num_notifications) - { - return; - } - } - - $sql = 'UPDATE ' . $this->user_notifications_table . " - SET notify = 0 - WHERE item_type = '" . $this->db->sql_escape($item_type) . "' - AND item_id = " . (int) $item_id . ' - AND user_id = ' .(int) $user_id . " - AND method = '" . $this->db->sql_escape($method) . "'"; - $this->db->sql_query($sql); - - if (!$this->db->sql_affectedrows()) - { - $sql = 'INSERT INTO ' . $this->user_notifications_table . ' ' . - $this->db->sql_build_array('INSERT', array( - 'item_type' => $item_type, - 'item_id' => (int) $item_id, - 'user_id' => (int) $user_id, - 'method' => $method, - 'notify' => 0, - )); - $this->db->sql_query($sql); - } - } - - /** - * Disable all notifications of a certain type - * - * This should be called when an extension which has notification types - * is disabled so that all those notifications are hidden and do not - * cause errors - * - * @param string $notification_type_name Type identifier of the subscription - */ - public function disable_notifications($notification_type_name) - { - $sql = 'UPDATE ' . $this->notification_types_table . " - SET notification_type_enabled = 0 - WHERE notification_type_name = '" . $this->db->sql_escape($notification_type_name) . "'"; - $this->db->sql_query($sql); - } - - /** - * Purge all notifications of a certain type - * - * This should be called when an extension which has notification types - * is purged so that all those notifications are removed - * - * @param string $notification_type_name Type identifier of the subscription - */ - public function purge_notifications($notification_type_name) - { - $notification_type_id = $this->get_notification_type_id($notification_type_name); - - $sql = 'DELETE FROM ' . $this->notifications_table . ' - WHERE notification_type_id = ' . (int) $notification_type_id; - $this->db->sql_query($sql); - - $sql = 'DELETE FROM ' . $this->notification_types_table . ' - WHERE notification_type_id = ' . (int) $notification_type_id; - $this->db->sql_query($sql); - - $this->cache->destroy('notification_type_ids'); - } - - /** - * Enable all notifications of a certain type - * - * This should be called when an extension which has notification types - * that was disabled is re-enabled so that all those notifications that - * were hidden are shown again - * - * @param string $notification_type_name Type identifier of the subscription - */ - public function enable_notifications($notification_type_name) - { - $sql = 'UPDATE ' . $this->notification_types_table . " - SET notification_type_enabled = 1 - WHERE notification_type_name = '" . $this->db->sql_escape($notification_type_name) . "'"; - $this->db->sql_query($sql); - } - - /** - * Delete all notifications older than a certain time - * - * @param int $timestamp Unix timestamp to delete all notifications that were created before - */ - public function prune_notifications($timestamp) - { - $sql = 'DELETE FROM ' . $this->notifications_table . ' - WHERE notification_time < ' . (int) $timestamp; - $this->db->sql_query($sql); - } - - /** - * Helper to get the notifications item type class and set it up - */ - public function get_item_type_class($notification_type_name, $data = array()) - { - $notification_type_name = (strpos($notification_type_name, 'notification.type.') === 0) ? $notification_type_name : 'notification.type.' . $notification_type_name; - - $item = $this->load_object($notification_type_name); - - $item->set_initial_data($data); - - return $item; - } - - /** - * Helper to get the notifications method class and set it up - */ - public function get_method_class($method_name) - { - $method_name = (strpos($method_name, 'notification.method.') === 0) ? $method_name : 'notification.method.' . $method_name; - - return $this->load_object($method_name); - } - - /** - * Helper to load objects (notification types/methods) - */ - protected function load_object($object_name) - { - $object = $this->phpbb_container->get($object_name); - - if (method_exists($object, 'set_notification_manager')) - { - $object->set_notification_manager($this); - } - - return $object; - } - - /** - * Get the notification type id from the name - * - * @param string $notification_type_name The name - * @return int the notification_type_id - */ - public function get_notification_type_id($notification_type_name) - { - $notification_type_ids = $this->cache->get('notification_type_ids'); - - if ($notification_type_ids === false) - { - $notification_type_ids = array(); - - $sql = 'SELECT notification_type_id, notification_type_name - FROM ' . $this->notification_types_table; - $result = $this->db->sql_query($sql); - while ($row = $this->db->sql_fetchrow($result)) - { - $notification_type_ids[$row['notification_type_name']] = (int) $row['notification_type_id']; - } - $this->db->sql_freeresult($result); - - $this->cache->put('notification_type_ids', $notification_type_ids); - } - - if (!isset($notification_type_ids[$notification_type_name])) - { - if (!isset($this->notification_types[$notification_type_name]) && !isset($this->notification_types['notification.type.' . $notification_type_name])) - { - throw new phpbb_notification_exception($this->user->lang('NOTIFICATION_TYPE_NOT_EXIST', $notification_type_name)); - } - - $sql = 'INSERT INTO ' . $this->notification_types_table . ' ' . $this->db->sql_build_array('INSERT', array( - 'notification_type_name' => $notification_type_name, - 'notification_type_enabled' => 1, - )); - $this->db->sql_query($sql); - - $notification_type_ids[$notification_type_name] = (int) $this->db->sql_nextid(); - - $this->cache->put('notification_type_ids', $notification_type_ids); - } - - return $notification_type_ids[$notification_type_name]; - } - - /** - * Get notification type ids (as an array) - * - * @param array $notification_type_names Array of strings - * @return array Array of integers - */ - public function get_notification_type_ids(array $notification_type_names) - { - $notification_type_ids = array(); - - foreach ($notification_type_names as $name) - { - $notification_type_ids[$name] = $this->get_notification_type_id($name); - } - - return $notification_type_ids; - } -} diff --git a/phpBB/includes/notification/method/base.php b/phpBB/includes/notification/method/base.php deleted file mode 100644 index b633956d01..0000000000 --- a/phpBB/includes/notification/method/base.php +++ /dev/null @@ -1,116 +0,0 @@ -user_loader = $user_loader; - $this->db = $db; - $this->cache = $cache; - $this->user = $user; - $this->auth = $auth; - $this->config = $config; - $this->phpbb_root_path = $phpbb_root_path; - $this->php_ext = $php_ext; - } - - /** - * Set notification manager (required) - * - * @param phpbb_notification_manager $notification_manager - */ - public function set_notification_manager(phpbb_notification_manager $notification_manager) - { - $this->notification_manager = $notification_manager; - } - - /** - * Add a notification to the queue - * - * @param phpbb_notification_type_interface $notification - */ - public function add_to_queue(phpbb_notification_type_interface $notification) - { - $this->queue[] = $notification; - } - - /** - * Empty the queue - */ - protected function empty_queue() - { - $this->queue = array(); - } -} diff --git a/phpBB/includes/notification/method/email.php b/phpBB/includes/notification/method/email.php deleted file mode 100644 index 571b0ec656..0000000000 --- a/phpBB/includes/notification/method/email.php +++ /dev/null @@ -1,52 +0,0 @@ -config['email_enable'] && $this->user->data['user_email']; - } - - /** - * Parse the queue and notify the users - */ - public function notify() - { - return $this->notify_using_messenger(NOTIFY_EMAIL); - } -} diff --git a/phpBB/includes/notification/method/interface.php b/phpBB/includes/notification/method/interface.php deleted file mode 100644 index ef875942cc..0000000000 --- a/phpBB/includes/notification/method/interface.php +++ /dev/null @@ -1,48 +0,0 @@ -global_available() && $this->user->data['user_jabber']); - } - - /** - * Is this method available at all? - * This is checked before notifications are sent - */ - public function global_available() - { - return !( - empty($this->config['jab_enable']) || - empty($this->config['jab_host']) || - empty($this->config['jab_username']) || - empty($this->config['jab_password']) || - !@extension_loaded('xml') - ); - } - - public function notify() - { - if (!$this->global_available()) - { - return; - } - - return $this->notify_using_messenger(NOTIFY_IM, 'short/'); - } -} diff --git a/phpBB/includes/notification/method/messenger_base.php b/phpBB/includes/notification/method/messenger_base.php deleted file mode 100644 index 4966aa94bc..0000000000 --- a/phpBB/includes/notification/method/messenger_base.php +++ /dev/null @@ -1,100 +0,0 @@ -queue)) - { - return; - } - - // Load all users we want to notify (we need their email address) - $user_ids = $users = array(); - foreach ($this->queue as $notification) - { - $user_ids[] = $notification->user_id; - } - - // We do not send emails to banned users - if (!function_exists('phpbb_get_banned_user_ids')) - { - include($this->phpbb_root_path . 'includes/functions_user.' . $this->php_ext); - } - $banned_users = phpbb_get_banned_user_ids($user_ids); - - // Load all the users we need - $this->user_loader->load_users($user_ids); - - // Load the messenger - if (!class_exists('messenger')) - { - include($this->phpbb_root_path . 'includes/functions_messenger.' . $this->php_ext); - } - $messenger = new messenger(); - $board_url = generate_board_url(); - - // Time to go through the queue and send emails - foreach ($this->queue as $notification) - { - if ($notification->get_email_template() === false) - { - continue; - } - - $user = $this->user_loader->get_user($notification->user_id); - - if ($user['user_type'] == USER_IGNORE || in_array($notification->user_id, $banned_users)) - { - continue; - } - - $messenger->template($template_dir_prefix . $notification->get_email_template(), $user['user_lang']); - - $messenger->set_addresses($user); - - $messenger->assign_vars(array_merge(array( - 'USERNAME' => $user['username'], - - 'U_NOTIFICATION_SETTINGS' => generate_board_url() . '/ucp.' . $this->php_ext . '?i=ucp_notifications', - ), $notification->get_email_template_variables())); - - $messenger->send($notify_method); - } - - // Save the queue in the messenger class (has to be called or these emails could be lost?) - $messenger->save_queue(); - - // We're done, empty the queue - $this->empty_queue(); - } -} diff --git a/phpBB/includes/notification/type/approve_post.php b/phpBB/includes/notification/type/approve_post.php deleted file mode 100644 index 1a30781c35..0000000000 --- a/phpBB/includes/notification/type/approve_post.php +++ /dev/null @@ -1,140 +0,0 @@ - 'moderation_queue', - 'lang' => 'NOTIFICATION_TYPE_MODERATION_QUEUE', - 'group' => 'NOTIFICATION_GROUP_POSTING', - ); - - /** - * Is available - */ - public function is_available() - { - return !$this->auth->acl_get('m_approve'); - } - - /** - * Find the users who want to receive notifications - * - * @param array $post Data from - * - * @return array - */ - public function find_users_for_notification($post, $options = array()) - { - $options = array_merge(array( - 'ignore_users' => array(), - ), $options); - - $users = array(); - $users[$post['poster_id']] = array(''); - - $auth_read = $this->auth->acl_get_list(array_keys($users), 'f_read', $post['forum_id']); - - if (empty($auth_read)) - { - return array(); - } - - return $this->check_user_notification_options($auth_read[$post['forum_id']]['f_read'], array_merge($options, array( - 'item_type' => self::$notification_option['id'], - ))); - } - - /** - * Pre create insert array function - * This allows you to perform certain actions, like run a query - * and load data, before create_insert_array() is run. The data - * returned from this function will be sent to create_insert_array(). - * - * @param array $post Post data from submit_post - * @param array $notify_users Notify users list - * Formated from find_users_for_notification() - * @return array Whatever you want to send to create_insert_array(). - */ - public function pre_create_insert_array($post, $notify_users) - { - // In the parent class, this is used to check if the post is already - // read by a user and marks the notification read if it was marked read. - // Returning an empty array in effect, forces it to be marked as unread - // (and also saves a query) - return array(); - } - - /** - * Function for preparing the data for insertion in an SQL query - * (The service handles insertion) - * - * @param array $post Data from submit_post - * @param array $pre_create_data Data from pre_create_insert_array() - * - * @return array Array of data ready to be inserted into the database - */ - public function create_insert_array($post, $pre_create_data = array()) - { - $this->set_data('post_subject', $post['post_subject']); - - $data = parent::create_insert_array($post, $pre_create_data); - - $this->notification_time = $data['notification_time'] = time(); - - return $data; - } - - /** - * Get email template - * - * @return string|bool - */ - public function get_email_template() - { - return 'post_approved'; - } -} diff --git a/phpBB/includes/notification/type/approve_topic.php b/phpBB/includes/notification/type/approve_topic.php deleted file mode 100644 index e728e9ac30..0000000000 --- a/phpBB/includes/notification/type/approve_topic.php +++ /dev/null @@ -1,138 +0,0 @@ - 'moderation_queue', - 'lang' => 'NOTIFICATION_TYPE_MODERATION_QUEUE', - 'group' => 'NOTIFICATION_GROUP_POSTING', - ); - - /** - * Is available - */ - public function is_available() - { - return !$this->auth->acl_get('m_approve'); - } - - /** - * Find the users who want to receive notifications - * - * @param array $post Data from - * - * @return array - */ - public function find_users_for_notification($post, $options = array()) - { - $options = array_merge(array( - 'ignore_users' => array(), - ), $options); - - $users = array(); - $users[$post['poster_id']] = array(''); - - $auth_read = $this->auth->acl_get_list(array_keys($users), 'f_read', $post['forum_id']); - - if (empty($auth_read)) - { - return array(); - } - - return $this->check_user_notification_options($auth_read[$post['forum_id']]['f_read'], array_merge($options, array( - 'item_type' => self::$notification_option['id'], - ))); - } - - /** - * Pre create insert array function - * This allows you to perform certain actions, like run a query - * and load data, before create_insert_array() is run. The data - * returned from this function will be sent to create_insert_array(). - * - * @param array $post Post data from submit_post - * @param array $notify_users Notify users list - * Formated from find_users_for_notification() - * @return array Whatever you want to send to create_insert_array(). - */ - public function pre_create_insert_array($post, $notify_users) - { - // In the parent class, this is used to check if the post is already - // read by a user and marks the notification read if it was marked read. - // Returning an empty array in effect, forces it to be marked as unread - // (and also saves a query) - return array(); - } - - /** - * Function for preparing the data for insertion in an SQL query - * (The service handles insertion) - * - * @param array $post Data from submit_post - * @param array $pre_create_data Data from pre_create_insert_array() - * - * @return array Array of data ready to be inserted into the database - */ - public function create_insert_array($post, $pre_create_data = array()) - { - $data = parent::create_insert_array($post, $pre_create_data); - - $this->notification_time = $data['notification_time'] = time(); - - return $data; - } - - /** - * Get email template - * - * @return string|bool - */ - public function get_email_template() - { - return 'topic_approved'; - } -} diff --git a/phpBB/includes/notification/type/base.php b/phpBB/includes/notification/type/base.php deleted file mode 100644 index 46517f1c9b..0000000000 --- a/phpBB/includes/notification/type/base.php +++ /dev/null @@ -1,489 +0,0 @@ - forum_id, for post => topic_id, etc) - * user_id - * notification_read - * notification_time - * notification_data (special serialized field that each notification type can use to store stuff) - * - * @var array $data Notification row from the database - * This must be private, all interaction should use __get(), __set(), get_data(), set_data() - */ - private $data = array(); - - /** - * Notification Type Base Constructor - * - * @param phpbb_user_loader $user_loader - * @param phpbb_db_driver $db - * @param phpbb_cache_driver_interface $cache - * @param phpbb_user $user - * @param phpbb_auth $auth - * @param phpbb_config $config - * @param string $phpbb_root_path - * @param string $php_ext - * @param string $notification_types_table - * @param string $notifications_table - * @param string $user_notifications_table - * @return phpbb_notification_type_base - */ - public function __construct(phpbb_user_loader $user_loader, phpbb_db_driver $db, phpbb_cache_driver_interface $cache, $user, phpbb_auth $auth, phpbb_config $config, $phpbb_root_path, $php_ext, $notification_types_table, $notifications_table, $user_notifications_table) - { - $this->user_loader = $user_loader; - $this->db = $db; - $this->cache = $cache; - $this->user = $user; - $this->auth = $auth; - $this->config = $config; - - $this->phpbb_root_path = $phpbb_root_path; - $this->php_ext = $php_ext; - - $this->notification_types_table = $notification_types_table; - $this->notifications_table = $notifications_table; - $this->user_notifications_table = $user_notifications_table; - } - - /** - * Set notification manager (required) - * - * @param phpbb_notification_manager $notification_manager - */ - public function set_notification_manager(phpbb_notification_manager $notification_manager) - { - $this->notification_manager = $notification_manager; - - $this->notification_type_id = $this->notification_manager->get_notification_type_id($this->get_type()); - } - - /** - * Set initial data from the database - * - * @param array $data Row directly from the database - */ - public function set_initial_data($data = array()) - { - // The row from the database (unless this is a new notification we're going to add) - $this->data = $data; - $this->data['notification_data'] = (isset($this->data['notification_data'])) ? unserialize($this->data['notification_data']) : array(); - } - - /** - * Magic method to get data from this notification - * - * @param mixed $name - * @return mixed - */ - public function __get($name) - { - return (!isset($this->data[$name])) ? null : $this->data[$name]; - } - - - /** - * Magic method to set data on this notification - * - * @param mixed $name - * @return null - */ - public function __set($name, $value) - { - $this->data[$name] = $value; - } - - - /** - * Magic method to get a string of this notification - * - * Primarily for testing - * - * @param string $name - * @return mixed - */ - public function __toString() - { - return (!empty($this->data)) ? var_export($this->data, true) : $this->get_type(); - } - - /** - * Get special data (only important for the classes that extend this) - * - * @param string $name Name of the variable to get - * @return mixed - */ - protected function get_data($name) - { - return ($name === false) ? $this->data['notification_data'] : ((isset($this->data['notification_data'][$name])) ? $this->data['notification_data'][$name] : null); - } - - /** - * Set special data (only important for the classes that extend this) - * - * @param string $name Name of the variable to set - * @param mixed $value Value to set to the variable - * @return mixed - */ - protected function set_data($name, $value) - { - $this->data['notification_data'][$name] = $value; - } - - /** - * Function for preparing the data for insertion in an SQL query - * (The service handles insertion) - * - * @param array $type_data Data unique to this notification type - * @param array $pre_create_data Data from pre_create_insert_array() - * @return array Array of data ready to be inserted into the database - */ - public function create_insert_array($type_data, $pre_create_data = array()) - { - // Defaults - $this->data = array_merge(array( - 'item_id' => static::get_item_id($type_data), - 'notification_type_id' => $this->notification_type_id, - 'item_parent_id' => static::get_item_parent_id($type_data), - - 'notification_time' => time(), - 'notification_read' => false, - - 'notification_data' => array(), - ), $this->data); - - $data = $this->data; - - $data['notification_data'] = serialize($data['notification_data']); - - return $data; - } - - /** - * Function for preparing the data for update in an SQL query - * (The service handles insertion) - * - * @param array $type_data Data unique to this notification type - * @return array Array of data ready to be updated in the database - */ - public function create_update_array($type_data) - { - $data = $this->create_insert_array($type_data); - - // Unset data unique to each row - unset( - $data['notification_time'], // Also unsetting time, since it always tries to change the time to current (if you actually need to change the time, over-ride this function) - $data['notification_id'], - $data['notification_read'], - $data['user_id'] - ); - - return $data; - } - - /** - * Mark this item read - * - * @param bool $return True to return a string containing the SQL code to update this item, False to execute it (Default: False) - * @return string|null If $return is False, nothing will be returned, else the sql code to update this item - */ - public function mark_read($return = false) - { - return $this->mark(false, $return); - } - - /** - * Mark this item unread - * - * @param bool $return True to return a string containing the SQL code to update this item, False to execute it (Default: False) - * @return string|null If $return is False, nothing will be returned, else the sql code to update this item - */ - public function mark_unread($return = false) - { - return $this->mark(true, $return); - } - - /** - * Prepare to output the notification to the template - * - * @return array Template variables - */ - public function prepare_for_display() - { - if ($this->get_url()) - { - $u_mark_read = append_sid($this->phpbb_root_path . 'index.' . $this->php_ext, 'mark_notification=' . $this->notification_id); - } - else - { - $redirect = (($this->user->page['page_dir']) ? $this->user->page['page_dir'] . '/' : '') . $this->user->page['page_name'] . (($this->user->page['query_string']) ? '?' . $this->user->page['query_string'] : ''); - - $u_mark_read = append_sid($this->phpbb_root_path . 'index.' . $this->php_ext, 'mark_notification=' . $this->notification_id . '&redirect=' . urlencode($redirect)); - } - - return array( - 'NOTIFICATION_ID' => $this->notification_id, - - 'AVATAR' => $this->get_avatar(), - - 'FORMATTED_TITLE' => $this->get_title(), - - 'URL' => $this->get_url(), - 'TIME' => $this->user->format_date($this->notification_time), - - 'UNREAD' => !$this->notification_read, - - 'U_MARK_READ' => (!$this->notification_read) ? $u_mark_read : '', - ); - } - - /** - * -------------- Fall back functions ------------------- - */ - - /** - * URL to unsubscribe to this notification (fall back) - * - * @param string|bool $method Method name to unsubscribe from (email|jabber|etc), False to unsubscribe from all notifications for this item - */ - public function get_unsubscribe_url($method = false) - { - return false; - } - - /** - * Get the user's avatar (fall back) - * - * @return string - */ - public function get_avatar() - { - return ''; - } - - /** - * Get the special items to load (fall back) - * - * @return array - */ - public function get_load_special() - { - return array(); - } - - /** - * Load the special items (fall back) - */ - public function load_special($data, $notifications) - { - return; - } - - /** - * Is available (fall back) - * - * @return bool - */ - public function is_available() - { - return true; - } - - /** - * Pre create insert array function (fall back) - * - * @return array - */ - public function pre_create_insert_array($type_data, $notify_users) - { - return array(); - } - - /** - * -------------- Helper functions ------------------- - */ - - /** - * Find the users who want to receive notifications (helper) - * - * @param array $user_ids User IDs to check if they want to receive notifications - * (Bool False to check all users besides anonymous and bots (USER_IGNORE)) - * - * @return array - */ - protected function check_user_notification_options($user_ids = false, $options = array()) - { - $options = array_merge(array( - 'ignore_users' => array(), - 'item_type' => $this->get_type(), - 'item_id' => 0, // Global by default - ), $options); - - if ($user_ids === false) - { - $user_ids = array(); - - $sql = 'SELECT user_id - FROM ' . USERS_TABLE . ' - WHERE user_id <> ' . ANONYMOUS . ' - AND user_type <> ' . USER_IGNORE; - $result = $this->db->sql_query($sql); - while ($row = $this->db->sql_fetchrow($result)) - { - $user_ids[] = $row['user_id']; - } - $this->db->sql_freeresult($result); - } - - if (empty($user_ids)) - { - return array(); - } - - $rowset = $resulting_user_ids = array(); - - $sql = 'SELECT user_id, method, notify - FROM ' . $this->user_notifications_table . ' - WHERE ' . $this->db->sql_in_set('user_id', $user_ids) . " - AND item_type = '" . $this->db->sql_escape($options['item_type']) . "' - AND item_id = " . (int) $options['item_id']; - $result = $this->db->sql_query($sql); - - while ($row = $this->db->sql_fetchrow($result)) - { - $resulting_user_ids[] = $row['user_id']; - - if (!$row['notify'] || (isset($options['ignore_users'][$row['user_id']]) && in_array($row['method'], $options['ignore_users'][$row['user_id']]))) - { - continue; - } - - if (!isset($rowset[$row['user_id']])) - { - $rowset[$row['user_id']] = array(); - } - - $rowset[$row['user_id']][] = $row['method']; - } - - $this->db->sql_freeresult($result); - - foreach ($user_ids as $user_id) - { - if (!in_array($user_id, $resulting_user_ids) && !isset($options['ignore_users'][$user_id])) - { - // No rows at all for this user, default to '' - $rowset[$user_id] = array(''); - } - } - - return $rowset; - } - - /** - * Mark this item read/unread helper - * - * @param bool $unread Unread (True/False) (Default: False) - * @param bool $return True to return a string containing the SQL code to update this item, False to execute it (Default: False) - * @return string|null If $return is False, nothing will be returned, else the sql code to update this item - */ - protected function mark($unread = true, $return = false) - { - $this->notification_read = (bool) !$unread; - - $where = array( - 'notification_type_id = ' . (int) $this->notification_type_id, - 'item_id = ' . (int) $this->item_id, - 'user_id = ' . (int) $this->user_id, - ); - $where = implode(' AND ', $where); - - if ($return) - { - return $where; - } - - $sql = 'UPDATE ' . $this->notifications_table . ' - SET notification_read = ' . (int) $this->notification_read . ' - WHERE ' . $where; - $this->db->sql_query($sql); - } -} diff --git a/phpBB/includes/notification/type/bookmark.php b/phpBB/includes/notification/type/bookmark.php deleted file mode 100644 index ae2e75d3eb..0000000000 --- a/phpBB/includes/notification/type/bookmark.php +++ /dev/null @@ -1,138 +0,0 @@ - 'NOTIFICATION_TYPE_BOOKMARK', - 'group' => 'NOTIFICATION_GROUP_POSTING', - ); - - /** - * Is available - */ - public function is_available() - { - return $this->config['allow_bookmarks']; - } - - /** - * Find the users who want to receive notifications - * - * @param array $post Data from - * - * @return array - */ - public function find_users_for_notification($post, $options = array()) - { - $options = array_merge(array( - 'ignore_users' => array(), - ), $options); - - $users = array(); - - $sql = 'SELECT user_id - FROM ' . BOOKMARKS_TABLE . ' - WHERE ' . $this->db->sql_in_set('topic_id', $post['topic_id']) . ' - AND user_id <> ' . (int) $post['poster_id']; - $result = $this->db->sql_query($sql); - while ($row = $this->db->sql_fetchrow($result)) - { - $users[] = $row['user_id']; - } - $this->db->sql_freeresult($result); - - if (empty($users)) - { - return array(); - } - sort($users); - - $auth_read = $this->auth->acl_get_list($users, 'f_read', $post['forum_id']); - - if (empty($auth_read)) - { - return array(); - } - - $notify_users = $this->check_user_notification_options($auth_read[$post['forum_id']]['f_read'], $options); - - // Try to find the users who already have been notified about replies and have not read the topic since and just update their notifications - $update_notifications = array(); - $sql = 'SELECT n.* - FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . ' nt - WHERE n.notification_type_id = ' . (int) $this->notification_type_id . ' - AND n.item_parent_id = ' . (int) self::get_item_parent_id($post) . ' - AND n.notification_read = 0 - AND nt.notification_type_id = n.notification_type_id - AND nt.notification_type_enabled = 1'; - $result = $this->db->sql_query($sql); - while ($row = $this->db->sql_fetchrow($result)) - { - // Do not create a new notification - unset($notify_users[$row['user_id']]); - - $notification = $this->notification_manager->get_item_type_class($this->get_type(), $row); - $sql = 'UPDATE ' . $this->notifications_table . ' - SET ' . $this->db->sql_build_array('UPDATE', $notification->add_responders($post)) . ' - WHERE notification_id = ' . $row['notification_id']; - $this->db->sql_query($sql); - } - $this->db->sql_freeresult($result); - - return $notify_users; - } - - /** - * Get email template - * - * @return string|bool - */ - public function get_email_template() - { - return 'bookmark'; - } -} diff --git a/phpBB/includes/notification/type/disapprove_post.php b/phpBB/includes/notification/type/disapprove_post.php deleted file mode 100644 index 951c7e0254..0000000000 --- a/phpBB/includes/notification/type/disapprove_post.php +++ /dev/null @@ -1,120 +0,0 @@ - 'moderation_queue', - 'lang' => 'NOTIFICATION_TYPE_MODERATION_QUEUE', - 'group' => 'NOTIFICATION_GROUP_POSTING', - ); - - /** - * Get the HTML formatted title of this notification - * - * @return string - */ - public function get_title() - { - return $this->user->lang( - $this->language_key, - censor_text($this->get_data('topic_title')), - $this->get_data('disapprove_reason') - ); - } - - /** - * Get the url to this item - * - * @return string URL - */ - public function get_url() - { - return ''; - } - - /** - * Get email template variables - * - * @return array - */ - public function get_email_template_variables() - { - return array_merge(parent::get_email_template_variables(), array( - 'REASON' => htmlspecialchars_decode($this->get_data('disapprove_reason')), - )); - } - - /** - * Function for preparing the data for insertion in an SQL query - * (The service handles insertion) - * - * @param array $post Data from submit_post - * @param array $pre_create_data Data from pre_create_insert_array() - * - * @return array Array of data ready to be inserted into the database - */ - public function create_insert_array($post, $pre_create_data = array()) - { - $this->set_data('disapprove_reason', $post['disapprove_reason']); - - $data = parent::create_insert_array($post); - - $this->notification_time = $data['notification_time'] = time(); - - return $data; - } - - /** - * Get email template - * - * @return string|bool - */ - public function get_email_template() - { - return 'post_disapproved'; - } -} diff --git a/phpBB/includes/notification/type/disapprove_topic.php b/phpBB/includes/notification/type/disapprove_topic.php deleted file mode 100644 index 038e528797..0000000000 --- a/phpBB/includes/notification/type/disapprove_topic.php +++ /dev/null @@ -1,120 +0,0 @@ - 'moderation_queue', - 'lang' => 'NOTIFICATION_TYPE_MODERATION_QUEUE', - 'group' => 'NOTIFICATION_GROUP_POSTING', - ); - - /** - * Get the HTML formatted title of this notification - * - * @return string - */ - public function get_title() - { - return $this->user->lang( - $this->language_key, - censor_text($this->get_data('topic_title')), - $this->get_data('disapprove_reason') - ); - } - - /** - * Get the url to this item - * - * @return string URL - */ - public function get_url() - { - return ''; - } - - /** - * Get email template variables - * - * @return array - */ - public function get_email_template_variables() - { - return array_merge(parent::get_email_template_variables(), array( - 'REASON' => htmlspecialchars_decode($this->get_data('disapprove_reason')), - )); - } - - /** - * Function for preparing the data for insertion in an SQL query - * (The service handles insertion) - * - * @param array $post Data from submit_post - * @param array $pre_create_data Data from pre_create_insert_array() - * - * @return array Array of data ready to be inserted into the database - */ - public function create_insert_array($post, $pre_create_data = array()) - { - $this->set_data('disapprove_reason', $post['disapprove_reason']); - - $data = parent::create_insert_array($post, $pre_create_data); - - $this->notification_time = $data['notification_time'] = time(); - - return $data; - } - - /** - * Get email template - * - * @return string|bool - */ - public function get_email_template() - { - return 'topic_disapproved'; - } -} diff --git a/phpBB/includes/notification/type/interface.php b/phpBB/includes/notification/type/interface.php deleted file mode 100644 index a40fdafd09..0000000000 --- a/phpBB/includes/notification/type/interface.php +++ /dev/null @@ -1,189 +0,0 @@ - array of users and user types that should not receive notifications from this type because they've already been notified - * e.g.: array(2 => array(''), 3 => array('', 'email'), ...) - * - * @return array - */ - public function find_users_for_notification($type_data, $options); - - /** - * Users needed to query before this notification can be displayed - * - * @return array Array of user_ids - */ - public function users_to_query(); - - /** - * Get the special items to load - * - * @return array Data will be combined sent to load_special() so you can run a single query and get data required for this notification type - */ - public function get_load_special(); - - /** - * Load the special items - * - * @param array $data Data from get_load_special() - * @param array $notifications Array of notifications (key is notification_id, value is the notification objects) - */ - public function load_special($data, $notifications); - - /** - * Get the HTML formatted title of this notification - * - * @return string - */ - public function get_title(); - - /** - * Get the url to this item - * - * @return string URL - */ - public function get_url(); - - /** - * URL to unsubscribe to this notification - * - * @param string|bool $method Method name to unsubscribe from (email|jabber|etc), False to unsubscribe from all notifications for this item - */ - public function get_unsubscribe_url($method); - - /** - * Get the user's avatar (the user who caused the notification typically) - * - * @return string - */ - public function get_avatar(); - - /** - * Prepare to output the notification to the template - */ - public function prepare_for_display(); - - /** - * Get email template - * - * @return string|bool - */ - public function get_email_template(); - - /** - * Get email template variables - * - * @return array - */ - public function get_email_template_variables(); - - /** - * Pre create insert array function - * This allows you to perform certain actions, like run a query - * and load data, before create_insert_array() is run. The data - * returned from this function will be sent to create_insert_array(). - * - * @param array $type_data The type specific data - * @param array $notify_users Notify users list - * Formated from find_users_for_notification() - * @return array Whatever you want to send to create_insert_array(). - */ - public function pre_create_insert_array($type_data, $notify_users); - - /** - * Function for preparing the data for insertion in an SQL query - * (The service handles insertion) - * - * @param array $type_data The type specific data - * @param array $pre_create_data Data from pre_create_insert_array() - * - * @return array Array of data ready to be inserted into the database - */ - public function create_insert_array($type_data, $pre_create_data); - - /** - * Function for preparing the data for update in an SQL query - * (The service handles insertion) - * - * @param array $type_data Data unique to this notification type - * - * @return array Array of data ready to be updated in the database - */ - public function create_update_array($type_data); - - /** - * Mark this item read - * - * @param bool $return True to return a string containing the SQL code to update this item, False to execute it (Default: False) - * @return string - */ - public function mark_read($return); - - /** - * Mark this item unread - * - * @param bool $return True to return a string containing the SQL code to update this item, False to execute it (Default: False) - * @return string - */ - public function mark_unread($return); -} diff --git a/phpBB/includes/notification/type/pm.php b/phpBB/includes/notification/type/pm.php deleted file mode 100644 index b3db7ad5ad..0000000000 --- a/phpBB/includes/notification/type/pm.php +++ /dev/null @@ -1,184 +0,0 @@ - 'NOTIFICATION_TYPE_PM', - ); - - /** - * Is available - */ - public function is_available() - { - return ($this->config['allow_privmsg'] && $this->auth->acl_get('u_readpm')); - } - - /** - * Get the id of the - * - * @param array $pm The data from the private message - */ - public static function get_item_id($pm) - { - return (int) $pm['msg_id']; - } - - /** - * Get the id of the parent - * - * @param array $pm The data from the pm - */ - public static function get_item_parent_id($pm) - { - // No parent - return 0; - } - - /** - * Find the users who want to receive notifications - * - * @param array $pm Data from - * - * @return array - */ - public function find_users_for_notification($pm, $options = array()) - { - $options = array_merge(array( - 'ignore_users' => array(), - ), $options); - - if (!sizeof($pm['recipients'])) - { - return array(); - } - - unset($pm['recipients'][$pm['from_user_id']]); - - $this->user_loader->load_users(array_keys($pm['recipients'])); - - return $this->check_user_notification_options(array_keys($pm['recipients']), $options); - } - - /** - * Get the user's avatar - */ - public function get_avatar() - { - return $this->user_loader->get_avatar($this->get_data('from_user_id')); - } - - /** - * Get the HTML formatted title of this notification - * - * @return string - */ - public function get_title() - { - $username = $this->user_loader->get_username($this->get_data('from_user_id'), 'no_profile'); - - return $this->user->lang('NOTIFICATION_PM', $username, $this->get_data('message_subject')); - } - - /** - * Get email template - * - * @return string|bool - */ - public function get_email_template() - { - return 'privmsg_notify'; - } - - /** - * Get email template variables - * - * @return array - */ - public function get_email_template_variables() - { - $user_data = $this->user_loader->get_user($this->get_data('from_user_id')); - - return array( - 'AUTHOR_NAME' => htmlspecialchars_decode($user_data['username']), - 'SUBJECT' => htmlspecialchars_decode(censor_text($this->get_data('message_subject'))), - - 'U_VIEW_MESSAGE' => generate_board_url() . '/ucp.' . $this->php_ext . "?i=pm&mode=view&p={$this->item_id}", - ); - } - - /** - * Get the url to this item - * - * @return string URL - */ - public function get_url() - { - return append_sid($this->phpbb_root_path . 'ucp.' . $this->php_ext, "i=pm&mode=view&p={$this->item_id}"); - } - - /** - * Users needed to query before this notification can be displayed - * - * @return array Array of user_ids - */ - public function users_to_query() - { - return array($this->get_data('from_user_id')); - } - - /** - * Function for preparing the data for insertion in an SQL query - * (The service handles insertion) - * - * @param array $post Data from submit_post - * @param array $pre_create_data Data from pre_create_insert_array() - * - * @return array Array of data ready to be inserted into the database - */ - public function create_insert_array($pm, $pre_create_data = array()) - { - $this->set_data('from_user_id', $pm['from_user_id']); - - $this->set_data('message_subject', $pm['message_subject']); - - return parent::create_insert_array($pm, $pre_create_data); - } -} diff --git a/phpBB/includes/notification/type/post.php b/phpBB/includes/notification/type/post.php deleted file mode 100644 index 9207fd866e..0000000000 --- a/phpBB/includes/notification/type/post.php +++ /dev/null @@ -1,385 +0,0 @@ - 'NOTIFICATION_TYPE_POST', - 'group' => 'NOTIFICATION_GROUP_POSTING', - ); - - /** - * Is available - */ - public function is_available() - { - return $this->config['allow_topic_notify']; - } - - /** - * Get the id of the item - * - * @param array $post The data from the post - */ - public static function get_item_id($post) - { - return (int) $post['post_id']; - } - - /** - * Get the id of the parent - * - * @param array $post The data from the post - */ - public static function get_item_parent_id($post) - { - return (int) $post['topic_id']; - } - - /** - * Find the users who want to receive notifications - * - * @param array $post Data from - * - * @return array - */ - public function find_users_for_notification($post, $options = array()) - { - $options = array_merge(array( - 'ignore_users' => array(), - ), $options); - - $users = array(); - - $sql = 'SELECT user_id - FROM ' . TOPICS_WATCH_TABLE . ' - WHERE topic_id = ' . (int) $post['topic_id'] . ' - AND notify_status = ' . NOTIFY_YES . ' - AND user_id <> ' . (int) $post['poster_id']; - $result = $this->db->sql_query($sql); - while ($row = $this->db->sql_fetchrow($result)) - { - $users[] = $row['user_id']; - } - $this->db->sql_freeresult($result); - - $sql = 'SELECT user_id - FROM ' . FORUMS_WATCH_TABLE . ' - WHERE forum_id = ' . (int) $post['forum_id'] . ' - AND notify_status = ' . NOTIFY_YES . ' - AND user_id <> ' . (int) $post['poster_id']; - $result = $this->db->sql_query($sql); - while ($row = $this->db->sql_fetchrow($result)) - { - $users[] = $row['user_id']; - } - $this->db->sql_freeresult($result); - - if (empty($users)) - { - return array(); - } - - $users = array_unique($users); - sort($users); - - $auth_read = $this->auth->acl_get_list($users, 'f_read', $post['forum_id']); - - if (empty($auth_read)) - { - return array(); - } - - $notify_users = $this->check_user_notification_options($auth_read[$post['forum_id']]['f_read'], $options); - - // Try to find the users who already have been notified about replies and have not read the topic since and just update their notifications - $update_notifications = array(); - $sql = 'SELECT n.* - FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . ' nt - WHERE n.notification_type_id = ' . (int) $this->notification_type_id . ' - AND n.item_parent_id = ' . (int) self::get_item_parent_id($post) . ' - AND n.notification_read = 0 - AND nt.notification_type_id = n.notification_type_id - AND nt.notification_type_enabled = 1'; - $result = $this->db->sql_query($sql); - while ($row = $this->db->sql_fetchrow($result)) - { - // Do not create a new notification - unset($notify_users[$row['user_id']]); - - $notification = $this->notification_manager->get_item_type_class($this->get_type(), $row); - $sql = 'UPDATE ' . $this->notifications_table . ' - SET ' . $this->db->sql_build_array('UPDATE', $notification->add_responders($post)) . ' - WHERE notification_id = ' . $row['notification_id']; - $this->db->sql_query($sql); - } - $this->db->sql_freeresult($result); - - return $notify_users; - } - - /** - * Get the user's avatar - */ - public function get_avatar() - { - return $this->user_loader->get_avatar($this->get_data('poster_id')); - } - - /** - * Get the HTML formatted title of this notification - * - * @return string - */ - public function get_title() - { - $responders = $this->get_data('responders'); - $usernames = array(); - - if (!is_array($responders)) - { - $responders = array(); - } - - $responders = array_merge(array(array( - 'poster_id' => $this->get_data('poster_id'), - 'username' => $this->get_data('post_username'), - )), $responders); - - foreach ($responders as $responder) - { - if ($responder['username']) - { - $usernames[] = $responder['username']; - } - else - { - $usernames[] = $this->user_loader->get_username($responder['poster_id'], 'no_profile'); - } - } - - return $this->user->lang( - $this->language_key, - implode(', ', $usernames), - censor_text($this->get_data('topic_title')) - ); - } - - /** - * Get email template - * - * @return string|bool - */ - public function get_email_template() - { - return 'topic_notify'; - } - - /** - * Get email template variables - * - * @return array - */ - public function get_email_template_variables() - { - if ($this->get_data('post_username')) - { - $username = $this->get_data('post_username'); - } - else - { - $username = $this->user_loader->get_username($this->get_data('poster_id'), 'username'); - } - - return array( - 'AUTHOR_NAME' => htmlspecialchars_decode($username), - 'POST_SUBJECT' => htmlspecialchars_decode(censor_text($this->get_data('post_subject'))), - 'TOPIC_TITLE' => htmlspecialchars_decode(censor_text($this->get_data('topic_title'))), - - 'U_VIEW_POST' => generate_board_url() . "/viewtopic.{$this->php_ext}?p={$this->item_id}#p{$this->item_id}", - 'U_NEWEST_POST' => generate_board_url() . "/viewtopic.{$this->php_ext}?f={$this->get_data('forum_id')}&t={$this->item_parent_id}&view=unread#unread", - 'U_TOPIC' => generate_board_url() . "/viewtopic.{$this->php_ext}?f={$this->get_data('forum_id')}&t={$this->item_parent_id}", - 'U_VIEW_TOPIC' => generate_board_url() . "/viewtopic.{$this->php_ext}?f={$this->get_data('forum_id')}&t={$this->item_parent_id}", - 'U_FORUM' => generate_board_url() . "/viewforum.{$this->php_ext}?f={$this->get_data('forum_id')}", - 'U_STOP_WATCHING_TOPIC' => generate_board_url() . "/viewtopic.{$this->php_ext}?uid={$this->user_id}&f={$this->get_data('forum_id')}&t={$this->item_parent_id}&unwatch=topic", - ); - } - - /** - * Get the url to this item - * - * @return string URL - */ - public function get_url() - { - return append_sid($this->phpbb_root_path . 'viewtopic.' . $this->php_ext, "p={$this->item_id}#p{$this->item_id}"); - } - - /** - * Users needed to query before this notification can be displayed - * - * @return array Array of user_ids - */ - public function users_to_query() - { - $responders = $this->get_data('responders'); - $users = array( - $this->get_data('poster_id'), - ); - - if (is_array($responders)) - { - foreach ($responders as $responder) - { - $users[] = $responder['poster_id']; - } - } - - return $users; - } - - /** - * Pre create insert array function - * This allows you to perform certain actions, like run a query - * and load data, before create_insert_array() is run. The data - * returned from this function will be sent to create_insert_array(). - * - * @param array $post Post data from submit_post - * @param array $notify_users Notify users list - * Formated from find_users_for_notification() - * @return array Whatever you want to send to create_insert_array(). - */ - public function pre_create_insert_array($post, $notify_users) - { - if (!sizeof($notify_users)) - { - return array(); - } - - $tracking_data = array(); - $sql = 'SELECT user_id, mark_time FROM ' . TOPICS_TRACK_TABLE . ' - WHERE topic_id = ' . (int) $post['topic_id'] . ' - AND ' . $this->db->sql_in_set('user_id', array_keys($notify_users)); - $result = $this->db->sql_query($sql); - while ($row = $this->db->sql_fetchrow($result)) - { - $tracking_data[$row['user_id']] = $row['mark_time']; - } - - return $tracking_data; - } - - /** - * Function for preparing the data for insertion in an SQL query - * (The service handles insertion) - * - * @param array $post Data from submit_post - * @param array $pre_create_data Data from pre_create_insert_array() - * - * @return array Array of data ready to be inserted into the database - */ - public function create_insert_array($post, $pre_create_data = array()) - { - $this->set_data('poster_id', $post['poster_id']); - - $this->set_data('topic_title', $post['topic_title']); - - $this->set_data('post_subject', $post['post_subject']); - - $this->set_data('post_username', (($post['poster_id'] == ANONYMOUS) ? $post['post_username'] : '')); - - $this->set_data('forum_id', $post['forum_id']); - - $this->set_data('forum_name', $post['forum_name']); - - $this->notification_time = $post['post_time']; - - // Topics can be "read" before they are public (while awaiting approval). - // Make sure that if the user has read the topic, it's marked as read in the notification - if (isset($pre_create_data[$this->user_id]) && $pre_create_data[$this->user_id] >= $this->notification_time) - { - $this->notification_read = true; - } - - return parent::create_insert_array($post, $pre_create_data); - } - - /** - * Add responders to the notification - * - * @param mixed $post - */ - public function add_responders($post) - { - // Do not add them as a responder if they were the original poster that created the notification - if ($this->get_data('poster_id') == $post['poster_id']) - { - return array('notification_data' => serialize($this->get_data(false))); - } - - $responders = $this->get_data('responders'); - - $responders = ($responders === null) ? array() : $responders; - - foreach ($responders as $responder) - { - // Do not add them as a responder multiple times - if ($responder['poster_id'] == $post['poster_id']) - { - return array('notification_data' => serialize($this->get_data(false))); - } - } - - $responders[] = array( - 'poster_id' => $post['poster_id'], - 'username' => (($post['poster_id'] == ANONYMOUS) ? $post['post_username'] : ''), - ); - - $this->set_data('responders', $responders); - - return array('notification_data' => serialize($this->get_data(false))); - } -} diff --git a/phpBB/includes/notification/type/post_in_queue.php b/phpBB/includes/notification/type/post_in_queue.php deleted file mode 100644 index bc4b15cdc3..0000000000 --- a/phpBB/includes/notification/type/post_in_queue.php +++ /dev/null @@ -1,154 +0,0 @@ - 'needs_approval', - 'lang' => 'NOTIFICATION_TYPE_IN_MODERATION_QUEUE', - 'group' => 'NOTIFICATION_GROUP_MODERATION', - ); - - /** - * Permission to check for (in find_users_for_notification) - * - * @var string Permission name - */ - protected $permission = 'm_approve'; - - /** - * Is available - */ - public function is_available() - { - $has_permission = $this->auth->acl_getf($this->permission, true); - - return (!empty($has_permission)); - } - - /** - * Find the users who want to receive notifications - * - * @param array $post Data from the post - * - * @return array - */ - public function find_users_for_notification($post, $options = array()) - { - $options = array_merge(array( - 'ignore_users' => array(), - ), $options); - - // 0 is for global moderator permissions - $auth_approve = $this->auth->acl_get_list(false, $this->permission, array($post['forum_id'], 0)); - - if (empty($auth_approve)) - { - return array(); - } - - $has_permission = array(); - - if (isset($auth_approve[$post['forum_id']][$this->permission])) - { - $has_permission = $auth_approve[$post['forum_id']][$this->permission]; - } - - if (isset($auth_approve[0][$this->permission])) - { - $has_permission = array_unique(array_merge($has_permission, $auth_approve[0][$this->permission])); - } - sort($has_permission); - - $auth_read = $this->auth->acl_get_list($has_permission, 'f_read', $post['forum_id']); - if (empty($auth_read)) - { - return array(); - } - - return $this->check_user_notification_options($auth_read[$post['forum_id']]['f_read'], array_merge($options, array( - 'item_type' => self::$notification_option['id'], - ))); - } - - /** - * Get the url to this item - * - * @return string URL - */ - public function get_url() - { - return append_sid($this->phpbb_root_path . 'mcp.' . $this->php_ext, "i=queue&mode=approve_details&f={$this->get_data('forum_id')}&p={$this->item_id}"); - } - - /** - * Function for preparing the data for insertion in an SQL query - * (The service handles insertion) - * - * @param array $post Data from submit_post - * @param array $pre_create_data Data from pre_create_insert_array() - * - * @return array Array of data ready to be inserted into the database - */ - public function create_insert_array($post, $pre_create_data = array()) - { - $data = parent::create_insert_array($post, $pre_create_data); - - $this->notification_time = $data['notification_time'] = time(); - - return $data; - } - - /** - * Get email template - * - * @return string|bool - */ - public function get_email_template() - { - return 'post_in_queue'; - } -} diff --git a/phpBB/includes/notification/type/quote.php b/phpBB/includes/notification/type/quote.php deleted file mode 100644 index 0ed13f36fb..0000000000 --- a/phpBB/includes/notification/type/quote.php +++ /dev/null @@ -1,222 +0,0 @@ - 'NOTIFICATION_TYPE_QUOTE', - 'group' => 'NOTIFICATION_GROUP_POSTING', - ); - - /** - * Is available - */ - public function is_available() - { - return true; - } - - /** - * Find the users who want to receive notifications - * - * @param array $post Data from - * - * @return array - */ - public function find_users_for_notification($post, $options = array()) - { - $options = array_merge(array( - 'ignore_users' => array(), - ), $options); - - $usernames = false; - preg_match_all(self::$regular_expression_match, $post['post_text'], $usernames); - - if (empty($usernames[1])) - { - return array(); - } - - $usernames[1] = array_unique($usernames[1]); - - $usernames = array_map('utf8_clean_string', $usernames[1]); - - $users = array(); - - $sql = 'SELECT user_id - FROM ' . USERS_TABLE . ' - WHERE ' . $this->db->sql_in_set('username_clean', $usernames) . ' - AND user_id <> ' . (int) $post['poster_id']; - $result = $this->db->sql_query($sql); - while ($row = $this->db->sql_fetchrow($result)) - { - $users[] = $row['user_id']; - } - $this->db->sql_freeresult($result); - - if (empty($users)) - { - return array(); - } - sort($users); - - $auth_read = $this->auth->acl_get_list($users, 'f_read', $post['forum_id']); - - if (empty($auth_read)) - { - return array(); - } - - $notify_users = $this->check_user_notification_options($auth_read[$post['forum_id']]['f_read'], $options); - - // Try to find the users who already have been notified about replies and have not read the topic since and just update their notifications - $update_notifications = array(); - $sql = 'SELECT n.* - FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . ' nt - WHERE n.notification_type_id = ' . (int) $this->notification_type_id . ' - AND n.item_parent_id = ' . (int) self::get_item_parent_id($post) . ' - AND n.notification_read = 0 - AND nt.notification_type_id = n.notification_type_id - AND nt.notification_type_enabled = 1'; - $result = $this->db->sql_query($sql); - while ($row = $this->db->sql_fetchrow($result)) - { - // Do not create a new notification - unset($notify_users[$row['user_id']]); - - $notification = $this->notification_manager->get_item_type_class($this->get_type(), $row); - $sql = 'UPDATE ' . $this->notifications_table . ' - SET ' . $this->db->sql_build_array('UPDATE', $notification->add_responders($post)) . ' - WHERE notification_id = ' . $row['notification_id']; - $this->db->sql_query($sql); - } - $this->db->sql_freeresult($result); - - return $notify_users; - } - - /** - * Update a notification - * - * @param array $data Data specific for this type that will be updated - */ - public function update_notifications($post) - { - $old_notifications = array(); - $sql = 'SELECT n.user_id - FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . ' nt - WHERE n.notification_type_id = ' . (int) $this->notification_type_id . ' - AND n.item_id = ' . self::get_item_id($post) . ' - AND nt.notification_type_id = n.notification_type_id - AND nt.notification_type_enabled = 1'; - $result = $this->db->sql_query($sql); - while ($row = $this->db->sql_fetchrow($result)) - { - $old_notifications[] = $row['user_id']; - } - $this->db->sql_freeresult($result); - - // Find the new users to notify - $notifications = $this->find_users_for_notification($post); - - // Find the notifications we must delete - $remove_notifications = array_diff($old_notifications, array_keys($notifications)); - - // Find the notifications we must add - $add_notifications = array(); - foreach (array_diff(array_keys($notifications), $old_notifications) as $user_id) - { - $add_notifications[$user_id] = $notifications[$user_id]; - } - - // Add the necessary notifications - $this->notification_manager->add_notifications_for_users($this->get_type(), $post, $add_notifications); - - // Remove the necessary notifications - if (!empty($remove_notifications)) - { - $sql = 'DELETE FROM ' . $this->notifications_table . ' - WHERE notification_type_id = ' . (int) $this->notification_type_id . ' - AND item_id = ' . self::get_item_id($post) . ' - AND ' . $this->db->sql_in_set('user_id', $remove_notifications); - $this->db->sql_query($sql); - } - - // return true to continue with the update code in the notifications service (this will update the rest of the notifications) - return true; - } - - /** - * Get email template - * - * @return string|bool - */ - public function get_email_template() - { - return 'quote'; - } - - /** - * Get email template variables - * - * @return array - */ - public function get_email_template_variables() - { - $user_data = $this->user_loader->get_user($this->get_data('poster_id')); - - return array_merge(parent::get_email_template_variables(), array( - 'AUTHOR_NAME' => htmlspecialchars_decode($user_data['username']), - )); - } -} diff --git a/phpBB/includes/notification/type/report_pm.php b/phpBB/includes/notification/type/report_pm.php deleted file mode 100644 index 3fa73bab41..0000000000 --- a/phpBB/includes/notification/type/report_pm.php +++ /dev/null @@ -1,229 +0,0 @@ - 'report', - 'lang' => 'NOTIFICATION_TYPE_REPORT', - 'group' => 'NOTIFICATION_GROUP_MODERATION', - ); - - /** - * Get the id of the parent - * - * @param array $pm The data from the pm - */ - public static function get_item_parent_id($pm) - { - return (int) $pm['report_id']; - } - - /** - * Is this type available to the current user (defines whether or not it will be shown in the UCP Edit notification options) - * - * @return bool True/False whether or not this is available to the user - */ - public function is_available() - { - $m_approve = $this->auth->acl_getf($this->permission, true); - - return (!empty($m_approve)); - } - - - /** - * Find the users who want to receive notifications - * (copied from post_in_queue) - * - * @param array $post Data from the post - * - * @return array - */ - public function find_users_for_notification($post, $options = array()) - { - $options = array_merge(array( - 'ignore_users' => array(), - ), $options); - - // Global - $post['forum_id'] = 0; - - $auth_approve = $this->auth->acl_get_list(false, $this->permission, $post['forum_id']); - - if (empty($auth_approve)) - { - return array(); - } - - if (($key = array_search($this->user->data['user_id'], $auth_approve[$post['forum_id']][$this->permission]))) - { - unset($auth_approve[$post['forum_id']][$this->permission][$key]); - } - - return $this->check_user_notification_options($auth_approve[$post['forum_id']][$this->permission], array_merge($options, array( - 'item_type' => self::$notification_option['id'], - ))); - } - - /** - * Get email template - * - * @return string|bool - */ - public function get_email_template() - { - return 'report_pm'; - } - - /** - * Get email template variables - * - * @return array - */ - public function get_email_template_variables() - { - return array( - 'AUTHOR_NAME' => htmlspecialchars_decode($user_data['username']), - 'SUBJECT' => htmlspecialchars_decode(censor_text($this->get_data('message_subject'))), - - 'U_VIEW_REPORT' => generate_board_url() . "mcp.{$this->php_ext}?r={$this->item_parent_id}&i=pm_reports&mode=pm_report_details", - ); - } - - /** - * Get the url to this item - * - * @return string URL - */ - public function get_url() - { - return append_sid($this->phpbb_root_path . 'mcp.' . $this->php_ext, "r={$this->item_parent_id}&i=pm_reports&mode=pm_report_details"); - } - - /** - * Get the HTML formatted title of this notification - * - * @return string - */ - public function get_title() - { - $this->user->add_lang('mcp'); - - $username = $this->user_loader->get_username($this->get_data('reporter_id'), 'no_profile'); - - if ($this->get_data('report_text')) - { - return $this->user->lang( - $this->language_key, - $username, - censor_text($this->get_data('message_subject')), - $this->get_data('report_text') - ); - } - - if (isset($this->user->lang[$this->get_data('reason_title')])) - { - return $this->user->lang( - $this->language_key, - $username, - censor_text($this->get_data('message_subject')), - $this->user->lang[$this->get_data('reason_title')] - ); - } - - return $this->user->lang( - $this->language_key, - $username, - censor_text($this->get_data('message_subject')), - $this->get_data('reason_description') - ); - } - - /** - * Get the user's avatar - */ - public function get_avatar() - { - return $this->user_loader->get_avatar($this->get_data('reporter_id')); - } - - /** - * Users needed to query before this notification can be displayed - * - * @return array Array of user_ids - */ - public function users_to_query() - { - return array($this->get_data('reporter_id')); - } - - /** - * Function for preparing the data for insertion in an SQL query - * (The service handles insertion) - * - * @param array $post Data from submit_post - * @param array $pre_create_data Data from pre_create_insert_array() - * - * @return array Array of data ready to be inserted into the database - */ - public function create_insert_array($post, $pre_create_data = array()) - { - $this->set_data('reporter_id', $this->user->data['user_id']); - $this->set_data('reason_title', strtoupper($post['reason_title'])); - $this->set_data('reason_description', $post['reason_description']); - $this->set_data('report_text', $post['report_text']); - - return parent::create_insert_array($post, $pre_create_data); - } -} diff --git a/phpBB/includes/notification/type/report_pm_closed.php b/phpBB/includes/notification/type/report_pm_closed.php deleted file mode 100644 index 63dfa92064..0000000000 --- a/phpBB/includes/notification/type/report_pm_closed.php +++ /dev/null @@ -1,155 +0,0 @@ -user->data['user_id']) - { - return array(); - } - - return array($pm['reporter'] => array('')); - } - - /** - * Get email template - * - * @return string|bool - */ - public function get_email_template() - { - return false; - } - - /** - * Get email template variables - * - * @return array - */ - public function get_email_template_variables() - { - return array(); - } - - /** - * Get the url to this item - * - * @return string URL - */ - public function get_url() - { - return ''; - } - - /** - * Get the HTML formatted title of this notification - * - * @return string - */ - public function get_title() - { - $username = $this->user_loader->get_username($this->get_data('closer_id'), 'no_profile'); - - return $this->user->lang( - $this->language_key, - $username, - censor_text($this->get_data('message_subject')) - ); - } - - /** - * Get the user's avatar - */ - public function get_avatar() - { - return $this->get_user_avatar($this->get_data('closer_id')); - } - - /** - * Users needed to query before this notification can be displayed - * - * @return array Array of user_ids - */ - public function users_to_query() - { - return array($this->get_data('closer_id')); - } - - /** - * Function for preparing the data for insertion in an SQL query - * (The service handles insertion) - * - * @param array $pm PM Data - * @param array $pre_create_data Data from pre_create_insert_array() - * - * @return array Array of data ready to be inserted into the database - */ - public function create_insert_array($pm, $pre_create_data = array()) - { - $this->set_data('closer_id', $pm['closer_id']); - - $data = parent::create_insert_array($pm, $pre_create_data); - - $this->notification_time = $data['notification_time'] = time(); - - return $data; - } -} diff --git a/phpBB/includes/notification/type/report_post.php b/phpBB/includes/notification/type/report_post.php deleted file mode 100644 index de5c54a291..0000000000 --- a/phpBB/includes/notification/type/report_post.php +++ /dev/null @@ -1,196 +0,0 @@ - 'report', - 'lang' => 'NOTIFICATION_TYPE_REPORT', - 'group' => 'NOTIFICATION_GROUP_MODERATION', - ); - - /** - * Find the users who want to receive notifications - * - * @param array $post Data from the post - * - * @return array - */ - public function find_users_for_notification($post, $options = array()) - { - $notify_users = parent::find_users_for_notification($post, $options); - - // never notify reporter - unset($notify_users[$this->user->data['user_id']]); - - return $notify_users; - } - - /** - * Get email template - * - * @return string|bool - */ - public function get_email_template() - { - return 'report_post'; - } - - /** - * Get email template variables - * - * @return array - */ - public function get_email_template_variables() - { - $board_url = generate_board_url(); - - return array( - 'POST_SUBJECT' => htmlspecialchars_decode(censor_text($this->get_data('post_subject'))), - 'TOPIC_TITLE' => htmlspecialchars_decode(censor_text($this->get_data('topic_title'))), - - 'U_VIEW_REPORT' => "{$board_url}/mcp.{$this->php_ext}?f={$this->get_data('forum_id')}&p={$this->item_id}&i=reports&mode=report_details#reports", - 'U_VIEW_POST' => "{$board_url}/viewtopic.{$this->php_ext}?p={$this->item_id}#p{$this->item_id}", - 'U_NEWEST_POST' => "{$board_url}/viewtopic.{$this->php_ext}?f={$this->get_data('forum_id')}&t={$this->item_parent_id}&view=unread#unread", - 'U_TOPIC' => "{$board_url}/viewtopic.{$this->php_ext}?f={$this->get_data('forum_id')}&t={$this->item_parent_id}", - 'U_VIEW_TOPIC' => "{$board_url}/viewtopic.{$this->php_ext}?f={$this->get_data('forum_id')}&t={$this->item_parent_id}", - 'U_FORUM' => "{$board_url}/viewforum.{$this->php_ext}?f={$this->get_data('forum_id')}", - ); - } - - /** - * Get the url to this item - * - * @return string URL - */ - public function get_url() - { - return append_sid($this->phpbb_root_path . 'mcp.' . $this->php_ext, "f={$this->get_data('forum_id')}&p={$this->item_id}&i=reports&mode=report_details#reports"); - } - - /** - * Get the HTML formatted title of this notification - * - * @return string - */ - public function get_title() - { - $this->user->add_lang('mcp'); - - $username = $this->user_loader->get_username($this->get_data('reporter_id'), 'no_profile'); - - if ($this->get_data('report_text')) - { - return $this->user->lang( - $this->language_key, - $username, - censor_text($this->get_data('post_subject')), - $this->get_data('report_text') - ); - } - - if (isset($this->user->lang[$this->get_data('reason_title')])) - { - return $this->user->lang( - $this->language_key, - $username, - censor_text($this->get_data('post_subject')), - $this->user->lang[$this->get_data('reason_title')] - ); - } - - return $this->user->lang( - $this->language_key, - $username, - censor_text($this->get_data('post_subject')), - $this->get_data('reason_description') - ); - } - - /** - * Get the user's avatar - */ - public function get_avatar() - { - return $this->user_loader->get_avatar($this->get_data('reporter_id')); - } - - /** - * Users needed to query before this notification can be displayed - * - * @return array Array of user_ids - */ - public function users_to_query() - { - return array($this->get_data('reporter_id')); - } - - /** - * Function for preparing the data for insertion in an SQL query - * (The service handles insertion) - * - * @param array $post Data from submit_post - * @param array $pre_create_data Data from pre_create_insert_array() - * - * @return array Array of data ready to be inserted into the database - */ - public function create_insert_array($post, $pre_create_data = array()) - { - $this->set_data('reporter_id', $this->user->data['user_id']); - $this->set_data('reason_title', strtoupper($post['reason_title'])); - $this->set_data('reason_description', $post['reason_description']); - $this->set_data('report_text', $post['report_text']); - - return parent::create_insert_array($post, $pre_create_data); - } -} diff --git a/phpBB/includes/notification/type/report_post_closed.php b/phpBB/includes/notification/type/report_post_closed.php deleted file mode 100644 index 3916cd8db7..0000000000 --- a/phpBB/includes/notification/type/report_post_closed.php +++ /dev/null @@ -1,155 +0,0 @@ -user->data['user_id']) - { - return array(); - } - - return array($post['reporter'] => array('')); - } - - /** - * Get email template - * - * @return string|bool - */ - public function get_email_template() - { - return false; - } - - /** - * Get email template variables - * - * @return array - */ - public function get_email_template_variables() - { - return array(); - } - - /** - * Get the url to this item - * - * @return string URL - */ - public function get_url() - { - return ''; - } - - /** - * Get the HTML formatted title of this notification - * - * @return string - */ - public function get_title() - { - $username = $this->user_loader->get_username($this->get_data('closer_id'), 'no_profile'); - - return $this->user->lang( - $this->language_key, - $username, - censor_text($this->get_data('post_subject')) - ); - } - - /** - * Get the user's avatar - */ - public function get_avatar() - { - return $this->user_loader->get_avatar($this->get_data('closer_id')); - } - - /** - * Users needed to query before this notification can be displayed - * - * @return array Array of user_ids - */ - public function users_to_query() - { - return array($this->get_data('closer_id')); - } - - /** - * Function for preparing the data for insertion in an SQL query - * (The service handles insertion) - * - * @param array $post Data from submit_post - * @param array $pre_create_data Data from pre_create_insert_array() - * - * @return array Array of data ready to be inserted into the database - */ - public function create_insert_array($post, $pre_create_data = array()) - { - $this->set_data('closer_id', $post['closer_id']); - - $data = parent::create_insert_array($post, $pre_create_data); - - $this->notification_time = $data['notification_time'] = time(); - - return $data; - } -} diff --git a/phpBB/includes/notification/type/topic.php b/phpBB/includes/notification/type/topic.php deleted file mode 100644 index 22436d3fb1..0000000000 --- a/phpBB/includes/notification/type/topic.php +++ /dev/null @@ -1,277 +0,0 @@ - 'NOTIFICATION_TYPE_TOPIC', - 'group' => 'NOTIFICATION_GROUP_POSTING', - ); - - /** - * Is available - */ - public function is_available() - { - return $this->config['allow_forum_notify']; - } - - /** - * Get the id of the item - * - * @param array $post The data from the post - */ - public static function get_item_id($post) - { - return (int) $post['topic_id']; - } - - /** - * Get the id of the parent - * - * @param array $post The data from the post - */ - public static function get_item_parent_id($post) - { - return (int) $post['forum_id']; - } - - /** - * Find the users who want to receive notifications - * - * @param array $topic Data from the topic - * - * @return array - */ - public function find_users_for_notification($topic, $options = array()) - { - $options = array_merge(array( - 'ignore_users' => array(), - ), $options); - - $users = array(); - - $sql = 'SELECT user_id - FROM ' . FORUMS_WATCH_TABLE . ' - WHERE forum_id = ' . (int) $topic['forum_id'] . ' - AND notify_status = ' . NOTIFY_YES . ' - AND user_id <> ' . (int) $topic['poster_id']; - $result = $this->db->sql_query($sql); - while ($row = $this->db->sql_fetchrow($result)) - { - $users[] = $row['user_id']; - } - $this->db->sql_freeresult($result); - - if (empty($users)) - { - return array(); - } - - $auth_read = $this->auth->acl_get_list($users, 'f_read', $topic['forum_id']); - - if (empty($auth_read)) - { - return array(); - } - - return $this->check_user_notification_options($auth_read[$topic['forum_id']]['f_read'], $options); - } - - /** - * Get the user's avatar - */ - public function get_avatar() - { - return $this->user_loader->get_avatar($this->get_data('poster_id')); - } - - /** - * Get the HTML formatted title of this notification - * - * @return string - */ - public function get_title() - { - if ($this->get_data('post_username')) - { - $username = $this->get_data('post_username'); - } - else - { - $username = $this->user_loader->get_username($this->get_data('poster_id'), 'no_profile'); - } - - return $this->user->lang( - $this->language_key, - $username, - censor_text($this->get_data('topic_title')), - $this->get_data('forum_name') - ); - } - - /** - * Get email template - * - * @return string|bool - */ - public function get_email_template() - { - return 'newtopic_notify'; - } - - /** - * Get email template variables - * - * @return array - */ - public function get_email_template_variables() - { - $board_url = generate_board_url(); - - if ($this->get_data('post_username')) - { - $username = $this->get_data('post_username'); - } - else - { - $username = $this->user_loader->get_username($this->get_data('poster_id'), 'username'); - } - - return array( - 'AUTHOR_NAME' => htmlspecialchars_decode($username), - 'FORUM_NAME' => htmlspecialchars_decode($this->get_data('forum_name')), - 'TOPIC_TITLE' => htmlspecialchars_decode(censor_text($this->get_data('topic_title'))), - - 'U_TOPIC' => "{$board_url}/viewtopic.{$this->php_ext}?f={$this->item_parent_id}&t={$this->item_id}", - 'U_VIEW_TOPIC' => "{$board_url}/viewtopic.{$this->php_ext}?f={$this->item_parent_id}&t={$this->item_id}", - 'U_FORUM' => "{$board_url}/viewforum.{$this->php_ext}?f={$this->item_parent_id}", - 'U_STOP_WATCHING_FORUM' => "{$board_url}/viewforum.{$this->php_ext}?uid={$this->user_id}&f={$this->item_parent_id}&unwatch=forum", - ); - } - - /** - * Get the url to this item - * - * @return string URL - */ - public function get_url() - { - return append_sid($this->phpbb_root_path . 'viewtopic.' . $this->php_ext, "f={$this->item_parent_id}&t={$this->item_id}"); - } - - /** - * Users needed to query before this notification can be displayed - * - * @return array Array of user_ids - */ - public function users_to_query() - { - return array($this->get_data('poster_id')); - } - - /** - * Pre create insert array function - * This allows you to perform certain actions, like run a query - * and load data, before create_insert_array() is run. The data - * returned from this function will be sent to create_insert_array(). - * - * @param array $post Post data from submit_post - * @param array $notify_users Notify users list - * Formated from find_users_for_notification() - * @return array Whatever you want to send to create_insert_array(). - */ - public function pre_create_insert_array($post, $notify_users) - { - if (!sizeof($notify_users)) - { - return array(); - } - - $tracking_data = array(); - $sql = 'SELECT user_id, mark_time FROM ' . TOPICS_TRACK_TABLE . ' - WHERE topic_id = ' . (int) $post['topic_id'] . ' - AND ' . $this->db->sql_in_set('user_id', array_keys($notify_users)); - $result = $this->db->sql_query($sql); - while ($row = $this->db->sql_fetchrow($result)) - { - $tracking_data[$row['user_id']] = $row['mark_time']; - } - - return $tracking_data; - } - - /** - * Function for preparing the data for insertion in an SQL query - * (The service handles insertion) - * - * @param array $post Data from submit_post - * @param array $pre_create_data Data from pre_create_insert_array() - * - * @return array Array of data ready to be inserted into the database - */ - public function create_insert_array($post, $pre_create_data = array()) - { - $this->set_data('poster_id', $post['poster_id']); - - $this->set_data('topic_title', $post['topic_title']); - - $this->set_data('post_username', (($post['poster_id'] == ANONYMOUS) ? $post['post_username'] : '')); - - $this->set_data('forum_name', $post['forum_name']); - - $this->notification_time = $post['post_time']; - - // Topics can be "read" before they are public (while awaiting approval). - // Make sure that if the user has read the topic, it's marked as read in the notification - if (isset($pre_create_data[$this->user_id]) && $pre_create_data[$this->user_id] >= $this->notification_time) - { - $this->notification_read = true; - } - - return parent::create_insert_array($post, $pre_create_data); - } -} diff --git a/phpBB/includes/notification/type/topic_in_queue.php b/phpBB/includes/notification/type/topic_in_queue.php deleted file mode 100644 index f735e10c00..0000000000 --- a/phpBB/includes/notification/type/topic_in_queue.php +++ /dev/null @@ -1,154 +0,0 @@ - 'needs_approval', - 'lang' => 'NOTIFICATION_TYPE_IN_MODERATION_QUEUE', - 'group' => 'NOTIFICATION_GROUP_MODERATION', - ); - - /** - * Permission to check for (in find_users_for_notification) - * - * @var string Permission name - */ - protected $permission = 'm_approve'; - - /** - * Is available - */ - public function is_available() - { - $has_permission = $this->auth->acl_getf($this->permission, true); - - return (!empty($has_permission)); - } - - /** - * Find the users who want to receive notifications - * - * @param array $topic Data from the topic - * - * @return array - */ - public function find_users_for_notification($topic, $options = array()) - { - $options = array_merge(array( - 'ignore_users' => array(), - ), $options); - - // 0 is for global moderator permissions - $auth_approve = $this->auth->acl_get_list(false, 'm_approve', array($topic['forum_id'], 0)); - - if (empty($auth_approve)) - { - return array(); - } - - $has_permission = array(); - - if (isset($auth_approve[$topic['forum_id']][$this->permission])) - { - $has_permission = $auth_approve[$topic['forum_id']][$this->permission]; - } - - if (isset($auth_approve[0][$this->permission])) - { - $has_permission = array_unique(array_merge($has_permission, $auth_approve[0][$this->permission])); - } - sort($has_permission); - - $auth_read = $this->auth->acl_get_list($has_permission, 'f_read', $topic['forum_id']); - if (empty($auth_read)) - { - return array(); - } - - return $this->check_user_notification_options($auth_read[$topic['forum_id']]['f_read'], array_merge($options, array( - 'item_type' => self::$notification_option['id'], - ))); - } - - /** - * Get the url to this item - * - * @return string URL - */ - public function get_url() - { - return append_sid($this->phpbb_root_path . 'mcp.' . $this->php_ext, "i=queue&mode=approve_details&f={$this->item_parent_id}&t={$this->item_id}"); - } - - /** - * Function for preparing the data for insertion in an SQL query - * (The service handles insertion) - * - * @param array $topic Data from submit_post - * @param array $pre_create_data Data from pre_create_insert_array() - * - * @return array Array of data ready to be inserted into the database - */ - public function create_insert_array($topic, $pre_create_data = array()) - { - $data = parent::create_insert_array($topic, $pre_create_data); - - $this->notification_time = $data['notification_time'] = time(); - - return $data; - } - - /** - * Get email template - * - * @return string|bool - */ - public function get_email_template() - { - return 'topic_in_queue'; - } -} diff --git a/phpBB/includes/php/ini.php b/phpBB/includes/php/ini.php deleted file mode 100644 index 17e8c54a57..0000000000 --- a/phpBB/includes/php/ini.php +++ /dev/null @@ -1,175 +0,0 @@ -get($varname); - - if ($value === false) - { - return false; - } - - return trim($value); - } - - /** - * Gets configuration option value as a boolean. - * Interprets the string value 'off' as false. - * - * @param string $varname The configuration option name. - * @return bool False if configuration option does not exist. - * False if configuration option is disabled. - * True otherwise. - */ - public function get_bool($varname) - { - $value = $this->get_string($varname); - - if (empty($value) || strtolower($value) == 'off') - { - return false; - } - - return true; - } - - /** - * Gets configuration option value as an integer. - * - * @param string $varname The configuration option name. - * @return bool|int False if configuration option does not exist, - * false if configuration option value is not numeric, - * the configuration option value (integer) otherwise. - */ - public function get_int($varname) - { - $value = $this->get_string($varname); - - if (!is_numeric($value)) - { - return false; - } - - return (int) $value; - } - - /** - * Gets configuration option value as a float. - * - * @param string $varname The configuration option name. - * @return bool|float False if configuration option does not exist, - * false if configuration option value is not numeric, - * the configuration option value (float) otherwise. - */ - public function get_float($varname) - { - $value = $this->get_string($varname); - - if (!is_numeric($value)) - { - return false; - } - - return (float) $value; - } - - /** - * Gets configuration option value in bytes. - * Converts strings like '128M' to bytes (integer or float). - * - * @param string $varname The configuration option name. - * @return bool|int|float False if configuration option does not exist, - * false if configuration option value is not well-formed, - * the configuration option value otherwise. - */ - public function get_bytes($varname) - { - $value = $this->get_string($varname); - - if ($value === false) - { - return false; - } - - if (is_numeric($value)) - { - // Already in bytes. - return phpbb_to_numeric($value); - } - else if (strlen($value) < 2) - { - // Single character. - return false; - } - else if (strlen($value) < 3 && $value[0] === '-') - { - // Two characters but the first one is a minus. - return false; - } - - $value_lower = strtolower($value); - $value_numeric = phpbb_to_numeric($value); - - switch ($value_lower[strlen($value_lower) - 1]) - { - case 'g': - $value_numeric *= 1024; - case 'm': - $value_numeric *= 1024; - case 'k': - $value_numeric *= 1024; - break; - - default: - // It's not already in bytes (and thus numeric) - // and does not carry a unit. - return false; - } - - return $value_numeric; - } -} diff --git a/phpBB/includes/request/deactivated_super_global.php b/phpBB/includes/request/deactivated_super_global.php deleted file mode 100644 index cc05847ec7..0000000000 --- a/phpBB/includes/request/deactivated_super_global.php +++ /dev/null @@ -1,121 +0,0 @@ -request = $request; - $this->name = $name; - $this->super_global = $super_global; - } - - /** - * Calls trigger_error with the file and line number the super global was used in. - */ - private function error() - { - $file = ''; - $line = 0; - - $message = 'Illegal use of $' . $this->name . '. You must use the request class or request_var() to access input data. Found in %s on line %d. This error message was generated by deactivated_super_global.'; - - $backtrace = debug_backtrace(); - if (isset($backtrace[1])) - { - $file = $backtrace[1]['file']; - $line = $backtrace[1]['line']; - } - trigger_error(sprintf($message, $file, $line), E_USER_ERROR); - } - - /** - * Redirects isset to the correct request class call. - * - * @param string $offset The key of the super global being accessed. - * - * @return bool Whether the key on the super global exists. - */ - public function offsetExists($offset) - { - return $this->request->is_set($offset, $this->super_global); - } - - /**#@+ - * Part of the ArrayAccess implementation, will always result in a FATAL error. - */ - public function offsetGet($offset) - { - $this->error(); - } - - public function offsetSet($offset, $value) - { - $this->error(); - } - - public function offsetUnset($offset) - { - $this->error(); - } - /**#@-*/ - - /** - * Part of the Countable implementation, will always result in a FATAL error - */ - public function count() - { - $this->error(); - } - - /** - * Part of the Traversable/IteratorAggregate implementation, will always result in a FATAL error - */ - public function getIterator() - { - $this->error(); - } -} - diff --git a/phpBB/includes/request/interface.php b/phpBB/includes/request/interface.php deleted file mode 100644 index 741db35917..0000000000 --- a/phpBB/includes/request/interface.php +++ /dev/null @@ -1,139 +0,0 @@ - "a") - * then specifying array("var", 1) as the name will return "a". - * @param mixed $default A default value that is returned if the variable was not set. - * This function will always return a value of the same type as the default. - * @param bool $multibyte If $default is a string this paramater has to be true if the variable may contain any UTF-8 characters - * Default is false, causing all bytes outside the ASCII range (0-127) to be replaced with question marks - * @param phpbb_request_interface::POST|GET|REQUEST|COOKIE $super_global - * Specifies which super global should be used - * - * @return mixed The value of $_REQUEST[$var_name] run through {@link set_var set_var} to ensure that the type is the - * the same as that of $default. If the variable is not set $default is returned. - */ - public function variable($var_name, $default, $multibyte = false, $super_global = phpbb_request_interface::REQUEST); - - /** - * Shortcut method to retrieve SERVER variables. - * - * @param string|array $var_name See phpbb_request_interface::variable - * @param mixed $default See phpbb_request_interface::variable - * - * @return mixed The server variable value. - */ - public function server($var_name, $default = ''); - - /** - * Shortcut method to retrieve the value of client HTTP headers. - * - * @param string|array $header_name The name of the header to retrieve. - * @param mixed $default See phpbb_request_interface::variable - * - * @return mixed The header value. - */ - public function header($var_name, $default = ''); - - /** - * Checks whether a certain variable was sent via POST. - * To make sure that a request was sent using POST you should call this function - * on at least one variable. - * - * @param string $name The name of the form variable which should have a - * _p suffix to indicate the check in the code that creates the form too. - * - * @return bool True if the variable was set in a POST request, false otherwise. - */ - public function is_set_post($name); - - /** - * Checks whether a certain variable is set in one of the super global - * arrays. - * - * @param string $var Name of the variable - * @param phpbb_request_interface::POST|GET|REQUEST|COOKIE $super_global - * Specifies the super global which shall be checked - * - * @return bool True if the variable was sent as input - */ - public function is_set($var, $super_global = phpbb_request_interface::REQUEST); - - /** - * Checks whether the current request is an AJAX request (XMLHttpRequest) - * - * @return bool True if the current request is an ajax request - */ - public function is_ajax(); - - /** - * Checks if the current request is happening over HTTPS. - * - * @return bool True if the request is secure. - */ - public function is_secure(); - - /** - * Returns all variable names for a given super global - * - * @param phpbb_request_interface::POST|GET|REQUEST|COOKIE $super_global - * The super global from which names shall be taken - * - * @return array All variable names that are set for the super global. - * Pay attention when using these, they are unsanitised! - */ - public function variable_names($super_global = phpbb_request_interface::REQUEST); -} diff --git a/phpBB/includes/request/request.php b/phpBB/includes/request/request.php deleted file mode 100644 index ae3c526d89..0000000000 --- a/phpBB/includes/request/request.php +++ /dev/null @@ -1,415 +0,0 @@ - '_POST', - phpbb_request_interface::GET => '_GET', - phpbb_request_interface::REQUEST => '_REQUEST', - phpbb_request_interface::COOKIE => '_COOKIE', - phpbb_request_interface::SERVER => '_SERVER', - phpbb_request_interface::FILES => '_FILES', - ); - - /** - * @var array Stores original contents of $_REQUEST array. - */ - protected $original_request = null; - - /** - * @var - */ - protected $super_globals_disabled = false; - - /** - * @var array An associative array that has the value of super global constants as keys and holds their data as values. - */ - protected $input; - - /** - * @var phpbb_request_type_cast_helper_interface An instance of a type cast helper providing convenience methods for type conversions. - */ - protected $type_cast_helper; - - /** - * Initialises the request class, that means it stores all input data in {@link $input input} - * and then calls {@link phpbb_request_deactivated_super_global phpbb_request_deactivated_super_global} - */ - public function __construct(phpbb_request_type_cast_helper_interface $type_cast_helper = null, $disable_super_globals = true) - { - if ($type_cast_helper) - { - $this->type_cast_helper = $type_cast_helper; - } - else - { - $this->type_cast_helper = new phpbb_request_type_cast_helper(); - } - - foreach ($this->super_globals as $const => $super_global) - { - $this->input[$const] = isset($GLOBALS[$super_global]) ? $GLOBALS[$super_global] : array(); - } - - // simulate request_order = GP - $this->original_request = $this->input[phpbb_request_interface::REQUEST]; - $this->input[phpbb_request_interface::REQUEST] = $this->input[phpbb_request_interface::POST] + $this->input[phpbb_request_interface::GET]; - - if ($disable_super_globals) - { - $this->disable_super_globals(); - } - } - - /** - * Getter for $super_globals_disabled - * - * @return bool Whether super globals are disabled or not. - */ - public function super_globals_disabled() - { - return $this->super_globals_disabled; - } - - /** - * Disables access of super globals specified in $super_globals. - * This is achieved by overwriting the super globals with instances of {@link phpbb_request_deactivated_super_global phpbb_request_deactivated_super_global} - */ - public function disable_super_globals() - { - if (!$this->super_globals_disabled) - { - foreach ($this->super_globals as $const => $super_global) - { - unset($GLOBALS[$super_global]); - $GLOBALS[$super_global] = new phpbb_request_deactivated_super_global($this, $super_global, $const); - } - - $this->super_globals_disabled = true; - } - } - - /** - * Enables access of super globals specified in $super_globals if they were disabled by {@link disable_super_globals disable_super_globals}. - * This is achieved by making the super globals point to the data stored within this class in {@link $input input}. - */ - public function enable_super_globals() - { - if ($this->super_globals_disabled) - { - foreach ($this->super_globals as $const => $super_global) - { - $GLOBALS[$super_global] = $this->input[$const]; - } - - $GLOBALS['_REQUEST'] = $this->original_request; - - $this->super_globals_disabled = false; - } - } - - /** - * This function allows overwriting or setting a value in one of the super global arrays. - * - * Changes which are performed on the super globals directly will not have any effect on the results of - * other methods this class provides. Using this function should be avoided if possible! It will - * consume twice the the amount of memory of the value - * - * @param string $var_name The name of the variable that shall be overwritten - * @param mixed $value The value which the variable shall contain. - * If this is null the variable will be unset. - * @param phpbb_request_interface::POST|GET|REQUEST|COOKIE $super_global - * Specifies which super global shall be changed - */ - public function overwrite($var_name, $value, $super_global = phpbb_request_interface::REQUEST) - { - if (!isset($this->super_globals[$super_global])) - { - return; - } - - $this->type_cast_helper->add_magic_quotes($value); - - // setting to null means unsetting - if ($value === null) - { - unset($this->input[$super_global][$var_name]); - if (!$this->super_globals_disabled()) - { - unset($GLOBALS[$this->super_globals[$super_global]][$var_name]); - } - } - else - { - $this->input[$super_global][$var_name] = $value; - if (!$this->super_globals_disabled()) - { - $GLOBALS[$this->super_globals[$super_global]][$var_name] = $value; - } - } - - if (!$this->super_globals_disabled()) - { - unset($GLOBALS[$this->super_globals[$super_global]][$var_name]); - $GLOBALS[$this->super_globals[$super_global]][$var_name] = $value; - } - } - - /** - * Central type safe input handling function. - * All variables in GET or POST requests should be retrieved through this function to maximise security. - * - * @param string|array $var_name The form variable's name from which data shall be retrieved. - * If the value is an array this may be an array of indizes which will give - * direct access to a value at any depth. E.g. if the value of "var" is array(1 => "a") - * then specifying array("var", 1) as the name will return "a". - * @param mixed $default A default value that is returned if the variable was not set. - * This function will always return a value of the same type as the default. - * @param bool $multibyte If $default is a string this paramater has to be true if the variable may contain any UTF-8 characters - * Default is false, causing all bytes outside the ASCII range (0-127) to be replaced with question marks - * @param phpbb_request_interface::POST|GET|REQUEST|COOKIE $super_global - * Specifies which super global should be used - * - * @return mixed The value of $_REQUEST[$var_name] run through {@link set_var set_var} to ensure that the type is the - * the same as that of $default. If the variable is not set $default is returned. - */ - public function variable($var_name, $default, $multibyte = false, $super_global = phpbb_request_interface::REQUEST) - { - return $this->_variable($var_name, $default, $multibyte, $super_global, true); - } - - /** - * Get a variable, but without trimming strings. - * Same functionality as variable(), except does not run trim() on strings. - * This method should be used when handling passwords. - * - * @param string|array $var_name The form variable's name from which data shall be retrieved. - * If the value is an array this may be an array of indizes which will give - * direct access to a value at any depth. E.g. if the value of "var" is array(1 => "a") - * then specifying array("var", 1) as the name will return "a". - * @param mixed $default A default value that is returned if the variable was not set. - * This function will always return a value of the same type as the default. - * @param bool $multibyte If $default is a string this paramater has to be true if the variable may contain any UTF-8 characters - * Default is false, causing all bytes outside the ASCII range (0-127) to be replaced with question marks - * @param phpbb_request_interface::POST|GET|REQUEST|COOKIE $super_global - * Specifies which super global should be used - * - * @return mixed The value of $_REQUEST[$var_name] run through {@link set_var set_var} to ensure that the type is the - * the same as that of $default. If the variable is not set $default is returned. - */ - public function untrimmed_variable($var_name, $default, $multibyte, $super_global = phpbb_request_interface::REQUEST) - { - return $this->_variable($var_name, $default, $multibyte, $super_global, false); - } - - /** - * Shortcut method to retrieve SERVER variables. - * - * Also fall back to getenv(), some CGI setups may need it (probably not, but - * whatever). - * - * @param string|array $var_name See phpbb_request_interface::variable - * @param mixed $Default See phpbb_request_interface::variable - * - * @return mixed The server variable value. - */ - public function server($var_name, $default = '') - { - $multibyte = true; - - if ($this->is_set($var_name, phpbb_request_interface::SERVER)) - { - return $this->variable($var_name, $default, $multibyte, phpbb_request_interface::SERVER); - } - else - { - $var = getenv($var_name); - $this->type_cast_helper->recursive_set_var($var, $default, $multibyte); - return $var; - } - } - - /** - * Shortcut method to retrieve the value of client HTTP headers. - * - * @param string|array $header_name The name of the header to retrieve. - * @param mixed $default See phpbb_request_interface::variable - * - * @return mixed The header value. - */ - public function header($header_name, $default = '') - { - $var_name = 'HTTP_' . str_replace('-', '_', strtoupper($header_name)); - return $this->server($var_name, $default); - } - - /** - * Shortcut method to retrieve $_FILES variables - * - * @param string $form_name The name of the file input form element - * - * @return array The uploaded file's information or an empty array if the - * variable does not exist in _FILES. - */ - public function file($form_name) - { - return $this->variable($form_name, array('name' => 'none'), false, phpbb_request_interface::FILES); - } - - /** - * Checks whether a certain variable was sent via POST. - * To make sure that a request was sent using POST you should call this function - * on at least one variable. - * - * @param string $name The name of the form variable which should have a - * _p suffix to indicate the check in the code that creates the form too. - * - * @return bool True if the variable was set in a POST request, false otherwise. - */ - public function is_set_post($name) - { - return $this->is_set($name, phpbb_request_interface::POST); - } - - /** - * Checks whether a certain variable is set in one of the super global - * arrays. - * - * @param string $var Name of the variable - * @param phpbb_request_interface::POST|GET|REQUEST|COOKIE $super_global - * Specifies the super global which shall be checked - * - * @return bool True if the variable was sent as input - */ - public function is_set($var, $super_global = phpbb_request_interface::REQUEST) - { - return isset($this->input[$super_global][$var]); - } - - /** - * Checks whether the current request is an AJAX request (XMLHttpRequest) - * - * @return bool True if the current request is an ajax request - */ - public function is_ajax() - { - return $this->header('X-Requested-With') == 'XMLHttpRequest'; - } - - /** - * Checks if the current request is happening over HTTPS. - * - * @return bool True if the request is secure. - */ - public function is_secure() - { - return $this->server('HTTPS') == 'on'; - } - - /** - * Returns all variable names for a given super global - * - * @param phpbb_request_interface::POST|GET|REQUEST|COOKIE $super_global - * The super global from which names shall be taken - * - * @return array All variable names that are set for the super global. - * Pay attention when using these, they are unsanitised! - */ - public function variable_names($super_global = phpbb_request_interface::REQUEST) - { - if (!isset($this->input[$super_global])) - { - return array(); - } - - return array_keys($this->input[$super_global]); - } - - /** - * Helper function used by variable() and untrimmed_variable(). - * - * @param string|array $var_name The form variable's name from which data shall be retrieved. - * If the value is an array this may be an array of indizes which will give - * direct access to a value at any depth. E.g. if the value of "var" is array(1 => "a") - * then specifying array("var", 1) as the name will return "a". - * @param mixed $default A default value that is returned if the variable was not set. - * This function will always return a value of the same type as the default. - * @param bool $multibyte If $default is a string this paramater has to be true if the variable may contain any UTF-8 characters - * Default is false, causing all bytes outside the ASCII range (0-127) to be replaced with question marks - * @param phpbb_request_interface::POST|GET|REQUEST|COOKIE $super_global - * Specifies which super global should be used - * @param bool $trim Indicates whether trim() should be applied to string values. - * - * @return mixed The value of $_REQUEST[$var_name] run through {@link set_var set_var} to ensure that the type is the - * the same as that of $default. If the variable is not set $default is returned. - */ - protected function _variable($var_name, $default, $multibyte = false, $super_global = phpbb_request_interface::REQUEST, $trim = true) - { - $path = false; - - // deep direct access to multi dimensional arrays - if (is_array($var_name)) - { - $path = $var_name; - // make sure at least the variable name is specified - if (empty($path)) - { - return (is_array($default)) ? array() : $default; - } - // the variable name is the first element on the path - $var_name = array_shift($path); - } - - if (!isset($this->input[$super_global][$var_name])) - { - return (is_array($default)) ? array() : $default; - } - $var = $this->input[$super_global][$var_name]; - - if ($path) - { - // walk through the array structure and find the element we are looking for - foreach ($path as $key) - { - if (is_array($var) && isset($var[$key])) - { - $var = $var[$key]; - } - else - { - return (is_array($default)) ? array() : $default; - } - } - } - - $this->type_cast_helper->recursive_set_var($var, $default, $multibyte, $trim); - - return $var; - } -} diff --git a/phpBB/includes/request/type_cast_helper.php b/phpBB/includes/request/type_cast_helper.php deleted file mode 100644 index 1a5274ed14..0000000000 --- a/phpBB/includes/request/type_cast_helper.php +++ /dev/null @@ -1,194 +0,0 @@ -=')) - { - $this->strip = false; - } - else - { - $this->strip = (@get_magic_quotes_gpc()) ? true : false; - } - } - - /** - * Recursively applies addslashes to a variable. - * - * @param mixed &$var Variable passed by reference to which slashes will be added. - */ - public function addslashes_recursively(&$var) - { - if (is_string($var)) - { - $var = addslashes($var); - } - else if (is_array($var)) - { - $var_copy = $var; - $var = array(); - foreach ($var_copy as $key => $value) - { - if (is_string($key)) - { - $key = addslashes($key); - } - $var[$key] = $value; - - $this->addslashes_recursively($var[$key]); - } - } - } - - /** - * Recursively applies addslashes to a variable if magic quotes are turned on. - * - * @param mixed &$var Variable passed by reference to which slashes will be added. - */ - public function add_magic_quotes(&$var) - { - if ($this->strip) - { - $this->addslashes_recursively($var); - } - } - - /** - * Set variable $result to a particular type. - * - * @param mixed &$result The variable to fill - * @param mixed $var The contents to fill with - * @param mixed $type The variable type. Will be used with {@link settype()} - * @param bool $multibyte Indicates whether string values may contain UTF-8 characters. - * Default is false, causing all bytes outside the ASCII range (0-127) to be replaced with question marks. - * @param bool $trim Indicates whether trim() should be applied to string values. - * Default is true. - */ - public function set_var(&$result, $var, $type, $multibyte = false, $trim = true) - { - settype($var, $type); - $result = $var; - - if ($type == 'string') - { - $result = str_replace(array("\r\n", "\r", "\0"), array("\n", "\n", ''), $result); - - if ($trim) - { - $result = trim($result); - } - - $result = htmlspecialchars($result, ENT_COMPAT, 'UTF-8'); - - if ($multibyte) - { - $result = utf8_normalize_nfc($result); - } - - if (!empty($result)) - { - // Make sure multibyte characters are wellformed - if ($multibyte) - { - if (!preg_match('/^./u', $result)) - { - $result = ''; - } - } - else - { - // no multibyte, allow only ASCII (0-127) - $result = preg_replace('/[\x80-\xFF]/', '?', $result); - } - } - - $result = ($this->strip) ? stripslashes($result) : $result; - } - } - - /** - * Recursively sets a variable to a given type using {@link set_var set_var} - * - * @param string $var The value which shall be sanitised (passed by reference). - * @param mixed $default Specifies the type $var shall have. - * If it is an array and $var is not one, then an empty array is returned. - * Otherwise var is cast to the same type, and if $default is an array all - * keys and values are cast recursively using this function too. - * @param bool $multibyte Indicates whether string keys and values may contain UTF-8 characters. - * Default is false, causing all bytes outside the ASCII range (0-127) to - * be replaced with question marks. - * @param bool $trim Indicates whether trim() should be applied to string values. - * Default is true. - */ - public function recursive_set_var(&$var, $default, $multibyte, $trim = true) - { - if (is_array($var) !== is_array($default)) - { - $var = (is_array($default)) ? array() : $default; - return; - } - - if (!is_array($default)) - { - $type = gettype($default); - $this->set_var($var, $var, $type, $multibyte, $trim); - } - else - { - // make sure there is at least one key/value pair to use get the - // types from - if (empty($default)) - { - $var = array(); - return; - } - - list($default_key, $default_value) = each($default); - $value_type = gettype($default_value); - $key_type = gettype($default_key); - - $_var = $var; - $var = array(); - - foreach ($_var as $k => $v) - { - $this->set_var($k, $k, $key_type, $multibyte); - - $this->recursive_set_var($v, $default_value, $multibyte, $trim); - $var[$k] = $v; - } - } - } -} diff --git a/phpBB/includes/request/type_cast_helper_interface.php b/phpBB/includes/request/type_cast_helper_interface.php deleted file mode 100644 index 3920d16fc7..0000000000 --- a/phpBB/includes/request/type_cast_helper_interface.php +++ /dev/null @@ -1,63 +0,0 @@ -ignore_words)) - { - global $user, $phpEx; - - $words = array(); - - if (file_exists("{$user->lang_path}{$user->lang_name}/search_ignore_words.$phpEx")) - { - // include the file containing ignore words - include("{$user->lang_path}{$user->lang_name}/search_ignore_words.$phpEx"); - } - - $this->ignore_words = $words; - unset($words); - } - } - - /** - * Stores a list of synonyms that should be replaced in $this->match_synonym and $this->replace_synonym and caches them - */ - function get_synonyms() - { - if (!sizeof($this->match_synonym)) - { - global $user, $phpEx; - - $synonyms = array(); - - if (file_exists("{$user->lang_path}{$user->lang_name}/search_synonyms.$phpEx")) - { - // include the file containing synonyms - include("{$user->lang_path}{$user->lang_name}/search_synonyms.$phpEx"); - } - - $this->match_synonym = array_keys($synonyms); - $this->replace_synonym = array_values($synonyms); - - unset($synonyms); - } - } - - /** - * Retrieves cached search results - * - * @param int &$result_count will contain the number of all results for the search (not only for the current page) - * @param array &$id_ary is filled with the ids belonging to the requested page that are stored in the cache - * - * @return int SEARCH_RESULT_NOT_IN_CACHE or SEARCH_RESULT_IN_CACHE or SEARCH_RESULT_INCOMPLETE - */ - function obtain_ids($search_key, &$result_count, &$id_ary, &$start, $per_page, $sort_dir) - { - global $cache; - - if (!($stored_ids = $cache->get('_search_results_' . $search_key))) - { - // no search results cached for this search_key - return SEARCH_RESULT_NOT_IN_CACHE; - } - else - { - $result_count = $stored_ids[-1]; - $reverse_ids = ($stored_ids[-2] != $sort_dir) ? true : false; - $complete = true; - - // Change start parameter in case out of bounds - if ($result_count) - { - if ($start < 0) - { - $start = 0; - } - else if ($start >= $result_count) - { - $start = floor(($result_count - 1) / $per_page) * $per_page; - } - } - - // change the start to the actual end of the current request if the sort direction differs - // from the dirction in the cache and reverse the ids later - if ($reverse_ids) - { - $start = $result_count - $start - $per_page; - - // the user requested a page past the last index - if ($start < 0) - { - return SEARCH_RESULT_NOT_IN_CACHE; - } - } - - for ($i = $start, $n = $start + $per_page; ($i < $n) && ($i < $result_count); $i++) - { - if (!isset($stored_ids[$i])) - { - $complete = false; - } - else - { - $id_ary[] = $stored_ids[$i]; - } - } - unset($stored_ids); - - if ($reverse_ids) - { - $id_ary = array_reverse($id_ary); - } - - if (!$complete) - { - return SEARCH_RESULT_INCOMPLETE; - } - return SEARCH_RESULT_IN_CACHE; - } - } - - /** - * Caches post/topic ids - * - * @param array &$id_ary contains a list of post or topic ids that shall be cached, the first element - * must have the absolute index $start in the result set. - */ - function save_ids($search_key, $keywords, $author_ary, $result_count, &$id_ary, $start, $sort_dir) - { - global $cache, $config, $db, $user; - - $length = min(sizeof($id_ary), $config['search_block_size']); - - // nothing to cache so exit - if (!$length) - { - return; - } - - $store_ids = array_slice($id_ary, 0, $length); - - // create a new resultset if there is none for this search_key yet - // or add the ids to the existing resultset - if (!($store = $cache->get('_search_results_' . $search_key))) - { - // add the current keywords to the recent searches in the cache which are listed on the search page - if (!empty($keywords) || sizeof($author_ary)) - { - $sql = 'SELECT search_time - FROM ' . SEARCH_RESULTS_TABLE . ' - WHERE search_key = \'' . $db->sql_escape($search_key) . '\''; - $result = $db->sql_query($sql); - - if (!$db->sql_fetchrow($result)) - { - $sql_ary = array( - 'search_key' => $search_key, - 'search_time' => time(), - 'search_keywords' => $keywords, - 'search_authors' => ' ' . implode(' ', $author_ary) . ' ' - ); - - $sql = 'INSERT INTO ' . SEARCH_RESULTS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary); - $db->sql_query($sql); - } - $db->sql_freeresult($result); - } - - $sql = 'UPDATE ' . USERS_TABLE . ' - SET user_last_search = ' . time() . ' - WHERE user_id = ' . $user->data['user_id']; - $db->sql_query($sql); - - $store = array(-1 => $result_count, -2 => $sort_dir); - $id_range = range($start, $start + $length - 1); - } - else - { - // we use one set of results for both sort directions so we have to calculate the indizes - // for the reversed array and we also have to reverse the ids themselves - if ($store[-2] != $sort_dir) - { - $store_ids = array_reverse($store_ids); - $id_range = range($store[-1] - $start - $length, $store[-1] - $start - 1); - } - else - { - $id_range = range($start, $start + $length - 1); - } - } - - $store_ids = array_combine($id_range, $store_ids); - - // append the ids - if (is_array($store_ids)) - { - $store += $store_ids; - - // if the cache is too big - if (sizeof($store) - 2 > 20 * $config['search_block_size']) - { - // remove everything in front of two blocks in front of the current start index - for ($i = 0, $n = $id_range[0] - 2 * $config['search_block_size']; $i < $n; $i++) - { - if (isset($store[$i])) - { - unset($store[$i]); - } - } - - // remove everything after two blocks after the current stop index - end($id_range); - for ($i = $store[-1] - 1, $n = current($id_range) + 2 * $config['search_block_size']; $i > $n; $i--) - { - if (isset($store[$i])) - { - unset($store[$i]); - } - } - } - $cache->put('_search_results_' . $search_key, $store, $config['search_store_results']); - - $sql = 'UPDATE ' . SEARCH_RESULTS_TABLE . ' - SET search_time = ' . time() . ' - WHERE search_key = \'' . $db->sql_escape($search_key) . '\''; - $db->sql_query($sql); - } - - unset($store); - unset($store_ids); - unset($id_range); - } - - /** - * Removes old entries from the search results table and removes searches with keywords that contain a word in $words. - */ - function destroy_cache($words, $authors = false) - { - global $db, $cache, $config; - - // clear all searches that searched for the specified words - if (sizeof($words)) - { - $sql_where = ''; - foreach ($words as $word) - { - $sql_where .= " OR search_keywords " . $db->sql_like_expression($db->any_char . $word . $db->any_char); - } - - $sql = 'SELECT search_key - FROM ' . SEARCH_RESULTS_TABLE . " - WHERE search_keywords LIKE '%*%' $sql_where"; - $result = $db->sql_query($sql); - - while ($row = $db->sql_fetchrow($result)) - { - $cache->destroy('_search_results_' . $row['search_key']); - } - $db->sql_freeresult($result); - } - - // clear all searches that searched for the specified authors - if (is_array($authors) && sizeof($authors)) - { - $sql_where = ''; - foreach ($authors as $author) - { - $sql_where .= (($sql_where) ? ' OR ' : '') . 'search_authors ' . $db->sql_like_expression($db->any_char . ' ' . (int) $author . ' ' . $db->any_char); - } - - $sql = 'SELECT search_key - FROM ' . SEARCH_RESULTS_TABLE . " - WHERE $sql_where"; - $result = $db->sql_query($sql); - - while ($row = $db->sql_fetchrow($result)) - { - $cache->destroy('_search_results_' . $row['search_key']); - } - $db->sql_freeresult($result); - } - - $sql = 'DELETE - FROM ' . SEARCH_RESULTS_TABLE . ' - WHERE search_time < ' . (time() - $config['search_store_results']); - $db->sql_query($sql); - } -} diff --git a/phpBB/includes/search/fulltext_mysql.php b/phpBB/includes/search/fulltext_mysql.php deleted file mode 100644 index 7dc4da8ffe..0000000000 --- a/phpBB/includes/search/fulltext_mysql.php +++ /dev/null @@ -1,948 +0,0 @@ -config = $config; - $this->db = $db; - $this->user = $user; - - $this->word_length = array('min' => $this->config['fulltext_mysql_min_word_len'], 'max' => $this->config['fulltext_mysql_max_word_len']); - - /** - * Load the UTF tools - */ - if (!function_exists('utf8_strlen')) - { - include($phpbb_root_path . 'includes/utf/utf_tools.' . $phpEx); - } - - $error = false; - } - - /** - * Returns the name of this search backend to be displayed to administrators - * - * @return string Name - */ - public function get_name() - { - return 'MySQL Fulltext'; - } - - /** - * Returns the search_query - * - * @return string search query - */ - public function get_search_query() - { - return $this->search_query; - } - - /** - * Returns the common_words array - * - * @return array common words that are ignored by search backend - */ - public function get_common_words() - { - return $this->common_words; - } - - /** - * Returns the word_length array - * - * @return array min and max word length for searching - */ - public function get_word_length() - { - return $this->word_length; - } - - /** - * Checks for correct MySQL version and stores min/max word length in the config - * - * @return string|bool Language key of the error/incompatiblity occurred - */ - public function init() - { - if ($this->db->sql_layer != 'mysql4' && $this->db->sql_layer != 'mysqli') - { - return $this->user->lang['FULLTEXT_MYSQL_INCOMPATIBLE_DATABASE']; - } - - $result = $this->db->sql_query('SHOW TABLE STATUS LIKE \'' . POSTS_TABLE . '\''); - $info = $this->db->sql_fetchrow($result); - $this->db->sql_freeresult($result); - - $engine = ''; - if (isset($info['Engine'])) - { - $engine = $info['Engine']; - } - else if (isset($info['Type'])) - { - $engine = $info['Type']; - } - - $fulltext_supported = - $engine === 'MyISAM' || - // FULLTEXT is supported on InnoDB since MySQL 5.6.4 according to - // http://dev.mysql.com/doc/refman/5.6/en/innodb-storage-engine.html - $engine === 'InnoDB' && - phpbb_version_compare($this->db->sql_server_info(true), '5.6.4', '>='); - - if (!$fulltext_supported) - { - return $this->user->lang['FULLTEXT_MYSQL_NOT_SUPPORTED']; - } - - $sql = 'SHOW VARIABLES - LIKE \'ft\_%\''; - $result = $this->db->sql_query($sql); - - $mysql_info = array(); - while ($row = $this->db->sql_fetchrow($result)) - { - $mysql_info[$row['Variable_name']] = $row['Value']; - } - $this->db->sql_freeresult($result); - - set_config('fulltext_mysql_max_word_len', $mysql_info['ft_max_word_len']); - set_config('fulltext_mysql_min_word_len', $mysql_info['ft_min_word_len']); - - return false; - } - - /** - * Splits keywords entered by a user into an array of words stored in $this->split_words - * Stores the tidied search query in $this->search_query - * - * @param string &$keywords Contains the keyword as entered by the user - * @param string $terms is either 'all' or 'any' - * @return bool false if no valid keywords were found and otherwise true - */ - public function split_keywords(&$keywords, $terms) - { - if ($terms == 'all') - { - $match = array('#\sand\s#iu', '#\sor\s#iu', '#\snot\s#iu', '#(^|\s)\+#', '#(^|\s)-#', '#(^|\s)\|#'); - $replace = array(' +', ' |', ' -', ' +', ' -', ' |'); - - $keywords = preg_replace($match, $replace, $keywords); - } - - // Filter out as above - $split_keywords = preg_replace("#[\n\r\t]+#", ' ', trim(htmlspecialchars_decode($keywords))); - - // Split words - $split_keywords = preg_replace('#([^\p{L}\p{N}\'*"()])#u', '$1$1', str_replace('\'\'', '\' \'', trim($split_keywords))); - $matches = array(); - preg_match_all('#(?:[^\p{L}\p{N}*"()]|^)([+\-|]?(?:[\p{L}\p{N}*"()]+\'?)*[\p{L}\p{N}*"()])(?:[^\p{L}\p{N}*"()]|$)#u', $split_keywords, $matches); - $this->split_words = $matches[1]; - - // We limit the number of allowed keywords to minimize load on the database - if ($this->config['max_num_search_keywords'] && sizeof($this->split_words) > $this->config['max_num_search_keywords']) - { - trigger_error($this->user->lang('MAX_NUM_SEARCH_KEYWORDS_REFINE', $this->config['max_num_search_keywords'], sizeof($this->split_words))); - } - - // to allow phrase search, we need to concatenate quoted words - $tmp_split_words = array(); - $phrase = ''; - foreach ($this->split_words as $word) - { - if ($phrase) - { - $phrase .= ' ' . $word; - if (strpos($word, '"') !== false && substr_count($word, '"') % 2 == 1) - { - $tmp_split_words[] = $phrase; - $phrase = ''; - } - } - else if (strpos($word, '"') !== false && substr_count($word, '"') % 2 == 1) - { - $phrase = $word; - } - else - { - $tmp_split_words[] = $word; - } - } - if ($phrase) - { - $tmp_split_words[] = $phrase; - } - - $this->split_words = $tmp_split_words; - - unset($tmp_split_words); - unset($phrase); - - foreach ($this->split_words as $i => $word) - { - $clean_word = preg_replace('#^[+\-|"]#', '', $word); - - // check word length - $clean_len = utf8_strlen(str_replace('*', '', $clean_word)); - if (($clean_len < $this->config['fulltext_mysql_min_word_len']) || ($clean_len > $this->config['fulltext_mysql_max_word_len'])) - { - $this->common_words[] = $word; - unset($this->split_words[$i]); - } - } - - if ($terms == 'any') - { - $this->search_query = ''; - foreach ($this->split_words as $word) - { - if ((strpos($word, '+') === 0) || (strpos($word, '-') === 0) || (strpos($word, '|') === 0)) - { - $word = substr($word, 1); - } - $this->search_query .= $word . ' '; - } - } - else - { - $this->search_query = ''; - foreach ($this->split_words as $word) - { - if ((strpos($word, '+') === 0) || (strpos($word, '-') === 0)) - { - $this->search_query .= $word . ' '; - } - else if (strpos($word, '|') === 0) - { - $this->search_query .= substr($word, 1) . ' '; - } - else - { - $this->search_query .= '+' . $word . ' '; - } - } - } - - $this->search_query = utf8_htmlspecialchars($this->search_query); - - if ($this->search_query) - { - $this->split_words = array_values($this->split_words); - sort($this->split_words); - return true; - } - return false; - } - - /** - * Turns text into an array of words - * @param string $text contains post text/subject - */ - public function split_message($text) - { - // Split words - $text = preg_replace('#([^\p{L}\p{N}\'*])#u', '$1$1', str_replace('\'\'', '\' \'', trim($text))); - $matches = array(); - preg_match_all('#(?:[^\p{L}\p{N}*]|^)([+\-|]?(?:[\p{L}\p{N}*]+\'?)*[\p{L}\p{N}*])(?:[^\p{L}\p{N}*]|$)#u', $text, $matches); - $text = $matches[1]; - - // remove too short or too long words - $text = array_values($text); - for ($i = 0, $n = sizeof($text); $i < $n; $i++) - { - $text[$i] = trim($text[$i]); - if (utf8_strlen($text[$i]) < $this->config['fulltext_mysql_min_word_len'] || utf8_strlen($text[$i]) > $this->config['fulltext_mysql_max_word_len']) - { - unset($text[$i]); - } - } - - return array_values($text); - } - - /** - * Performs a search on keywords depending on display specific params. You have to run split_keywords() first - * - * @param string $type contains either posts or topics depending on what should be searched for - * @param string $fields contains either titleonly (topic titles should be searched), msgonly (only message bodies should be searched), firstpost (only subject and body of the first post should be searched) or all (all post bodies and subjects should be searched) - * @param string $terms is either 'all' (use query as entered, words without prefix should default to "have to be in field") or 'any' (ignore search query parts and just return all posts that contain any of the specified words) - * @param array $sort_by_sql contains SQL code for the ORDER BY part of a query - * @param string $sort_key is the key of $sort_by_sql for the selected sorting - * @param string $sort_dir is either a or d representing ASC and DESC - * @param string $sort_days specifies the maximum amount of days a post may be old - * @param array $ex_fid_ary specifies an array of forum ids which should not be searched - * @param string $post_visibility specifies which types of posts the user can view in which forums - * @param int $topic_id is set to 0 or a topic id, if it is not 0 then only posts in this topic should be searched - * @param array $author_ary an array of author ids if the author should be ignored during the search the array is empty - * @param string $author_name specifies the author match, when ANONYMOUS is also a search-match - * @param array &$id_ary passed by reference, to be filled with ids for the page specified by $start and $per_page, should be ordered - * @param int $start indicates the first index of the page - * @param int $per_page number of ids each page is supposed to contain - * @return boolean|int total number of results - */ - public function keyword_search($type, $fields, $terms, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $post_visibility, $topic_id, $author_ary, $author_name, &$id_ary, &$start, $per_page) - { - // No keywords? No posts - if (!$this->search_query) - { - return false; - } - - // generate a search_key from all the options to identify the results - $search_key = md5(implode('#', array( - implode(', ', $this->split_words), - $type, - $fields, - $terms, - $sort_days, - $sort_key, - $topic_id, - implode(',', $ex_fid_ary), - $post_visibility, - implode(',', $author_ary) - ))); - - if ($start < 0) - { - $start = 0; - } - - // try reading the results from cache - $result_count = 0; - if ($this->obtain_ids($search_key, $result_count, $id_ary, $start, $per_page, $sort_dir) == SEARCH_RESULT_IN_CACHE) - { - return $result_count; - } - - $id_ary = array(); - - $join_topic = ($type == 'posts') ? false : true; - - // Build sql strings for sorting - $sql_sort = $sort_by_sql[$sort_key] . (($sort_dir == 'a') ? ' ASC' : ' DESC'); - $sql_sort_table = $sql_sort_join = ''; - - switch ($sql_sort[0]) - { - case 'u': - $sql_sort_table = USERS_TABLE . ' u, '; - $sql_sort_join = ($type == 'posts') ? ' AND u.user_id = p.poster_id ' : ' AND u.user_id = t.topic_poster '; - break; - - case 't': - $join_topic = true; - break; - - case 'f': - $sql_sort_table = FORUMS_TABLE . ' f, '; - $sql_sort_join = ' AND f.forum_id = p.forum_id '; - break; - } - - // Build some display specific sql strings - switch ($fields) - { - case 'titleonly': - $sql_match = 'p.post_subject'; - $sql_match_where = ' AND p.post_id = t.topic_first_post_id'; - $join_topic = true; - break; - - case 'msgonly': - $sql_match = 'p.post_text'; - $sql_match_where = ''; - break; - - case 'firstpost': - $sql_match = 'p.post_subject, p.post_text'; - $sql_match_where = ' AND p.post_id = t.topic_first_post_id'; - $join_topic = true; - break; - - default: - $sql_match = 'p.post_subject, p.post_text'; - $sql_match_where = ''; - break; - } - - $sql_select = (!$result_count) ? 'SQL_CALC_FOUND_ROWS ' : ''; - $sql_select = ($type == 'posts') ? $sql_select . 'p.post_id' : 'DISTINCT ' . $sql_select . 't.topic_id'; - $sql_from = ($join_topic) ? TOPICS_TABLE . ' t, ' : ''; - $field = ($type == 'posts') ? 'post_id' : 'topic_id'; - if (sizeof($author_ary) && $author_name) - { - // first one matches post of registered users, second one guests and deleted users - $sql_author = ' AND (' . $this->db->sql_in_set('p.poster_id', array_diff($author_ary, array(ANONYMOUS)), false, true) . ' OR p.post_username ' . $author_name . ')'; - } - else if (sizeof($author_ary)) - { - $sql_author = ' AND ' . $this->db->sql_in_set('p.poster_id', $author_ary); - } - else - { - $sql_author = ''; - } - - $sql_where_options = $sql_sort_join; - $sql_where_options .= ($topic_id) ? ' AND p.topic_id = ' . $topic_id : ''; - $sql_where_options .= ($join_topic) ? ' AND t.topic_id = p.topic_id' : ''; - $sql_where_options .= (sizeof($ex_fid_ary)) ? ' AND ' . $this->db->sql_in_set('p.forum_id', $ex_fid_ary, true) : ''; - $sql_where_options .= ' AND ' . $post_visibility; - $sql_where_options .= $sql_author; - $sql_where_options .= ($sort_days) ? ' AND p.post_time >= ' . (time() - ($sort_days * 86400)) : ''; - $sql_where_options .= $sql_match_where; - - $sql = "SELECT $sql_select - FROM $sql_from$sql_sort_table" . POSTS_TABLE . " p - WHERE MATCH ($sql_match) AGAINST ('" . $this->db->sql_escape(htmlspecialchars_decode($this->search_query)) . "' IN BOOLEAN MODE) - $sql_where_options - ORDER BY $sql_sort"; - $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); - - while ($row = $this->db->sql_fetchrow($result)) - { - $id_ary[] = (int) $row[$field]; - } - $this->db->sql_freeresult($result); - - $id_ary = array_unique($id_ary); - - // if the total result count is not cached yet, retrieve it from the db - if (!$result_count) - { - $sql_found_rows = 'SELECT FOUND_ROWS() as result_count'; - $result = $this->db->sql_query($sql_found_rows); - $result_count = (int) $this->db->sql_fetchfield('result_count'); - $this->db->sql_freeresult($result); - - if (!$result_count) - { - return false; - } - } - - if ($start >= $result_count) - { - $start = floor(($result_count - 1) / $per_page) * $per_page; - - $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); - - while ($row = $this->db->sql_fetchrow($result)) - { - $id_ary[] = (int) $row[$field]; - } - $this->db->sql_freeresult($result); - - $id_ary = array_unique($id_ary); - } - - // store the ids, from start on then delete anything that isn't on the current page because we only need ids for one page - $this->save_ids($search_key, implode(' ', $this->split_words), $author_ary, $result_count, $id_ary, $start, $sort_dir); - $id_ary = array_slice($id_ary, 0, (int) $per_page); - - return $result_count; - } - - /** - * Performs a search on an author's posts without caring about message contents. Depends on display specific params - * - * @param string $type contains either posts or topics depending on what should be searched for - * @param boolean $firstpost_only if true, only topic starting posts will be considered - * @param array $sort_by_sql contains SQL code for the ORDER BY part of a query - * @param string $sort_key is the key of $sort_by_sql for the selected sorting - * @param string $sort_dir is either a or d representing ASC and DESC - * @param string $sort_days specifies the maximum amount of days a post may be old - * @param array $ex_fid_ary specifies an array of forum ids which should not be searched - * @param string $post_visibility specifies which types of posts the user can view in which forums - * @param int $topic_id is set to 0 or a topic id, if it is not 0 then only posts in this topic should be searched - * @param array $author_ary an array of author ids - * @param string $author_name specifies the author match, when ANONYMOUS is also a search-match - * @param array &$id_ary passed by reference, to be filled with ids for the page specified by $start and $per_page, should be ordered - * @param int $start indicates the first index of the page - * @param int $per_page number of ids each page is supposed to contain - * @return boolean|int total number of results - */ - public function author_search($type, $firstpost_only, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $m_approve_fid_ary, $topic_id, $author_ary, $author_name, &$id_ary, &$start, $per_page) - { - // No author? No posts - if (!sizeof($author_ary)) - { - return 0; - } - - // generate a search_key from all the options to identify the results - $search_key = md5(implode('#', array( - '', - $type, - ($firstpost_only) ? 'firstpost' : '', - '', - '', - $sort_days, - $sort_key, - $topic_id, - implode(',', $ex_fid_ary), - $post_visibility, - implode(',', $author_ary), - $author_name, - ))); - - if ($start < 0) - { - $start = 0; - } - - // try reading the results from cache - $result_count = 0; - if ($this->obtain_ids($search_key, $result_count, $id_ary, $start, $per_page, $sort_dir) == SEARCH_RESULT_IN_CACHE) - { - return $result_count; - } - - $id_ary = array(); - - // Create some display specific sql strings - if ($author_name) - { - // first one matches post of registered users, second one guests and deleted users - $sql_author = '(' . $this->db->sql_in_set('p.poster_id', array_diff($author_ary, array(ANONYMOUS)), false, true) . ' OR p.post_username ' . $author_name . ')'; - } - else - { - $sql_author = $this->db->sql_in_set('p.poster_id', $author_ary); - } - $sql_fora = (sizeof($ex_fid_ary)) ? ' AND ' . $this->db->sql_in_set('p.forum_id', $ex_fid_ary, true) : ''; - $sql_topic_id = ($topic_id) ? ' AND p.topic_id = ' . (int) $topic_id : ''; - $sql_time = ($sort_days) ? ' AND p.post_time >= ' . (time() - ($sort_days * 86400)) : ''; - $sql_firstpost = ($firstpost_only) ? ' AND p.post_id = t.topic_first_post_id' : ''; - - // Build sql strings for sorting - $sql_sort = $sort_by_sql[$sort_key] . (($sort_dir == 'a') ? ' ASC' : ' DESC'); - $sql_sort_table = $sql_sort_join = ''; - switch ($sql_sort[0]) - { - case 'u': - $sql_sort_table = USERS_TABLE . ' u, '; - $sql_sort_join = ($type == 'posts') ? ' AND u.user_id = p.poster_id ' : ' AND u.user_id = t.topic_poster '; - break; - - case 't': - $sql_sort_table = ($type == 'posts' && !$firstpost_only) ? TOPICS_TABLE . ' t, ' : ''; - $sql_sort_join = ($type == 'posts' && !$firstpost_only) ? ' AND t.topic_id = p.topic_id ' : ''; - break; - - case 'f': - $sql_sort_table = FORUMS_TABLE . ' f, '; - $sql_sort_join = ' AND f.forum_id = p.forum_id '; - break; - } - - $m_approve_fid_sql = ' AND ' . $post_visibility; - - // If the cache was completely empty count the results - $calc_results = ($result_count) ? '' : 'SQL_CALC_FOUND_ROWS '; - - // Build the query for really selecting the post_ids - if ($type == 'posts') - { - $sql = "SELECT {$calc_results}p.post_id - FROM " . $sql_sort_table . POSTS_TABLE . ' p' . (($firstpost_only) ? ', ' . TOPICS_TABLE . ' t ' : ' ') . " - WHERE $sql_author - $sql_topic_id - $sql_firstpost - $m_approve_fid_sql - $sql_fora - $sql_sort_join - $sql_time - ORDER BY $sql_sort"; - $field = 'post_id'; - } - else - { - $sql = "SELECT {$calc_results}t.topic_id - FROM " . $sql_sort_table . TOPICS_TABLE . ' t, ' . POSTS_TABLE . " p - WHERE $sql_author - $sql_topic_id - $sql_firstpost - $m_approve_fid_sql - $sql_fora - AND t.topic_id = p.topic_id - $sql_sort_join - $sql_time - GROUP BY t.topic_id - ORDER BY $sql_sort"; - $field = 'topic_id'; - } - - // Only read one block of posts from the db and then cache it - $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); - - while ($row = $this->db->sql_fetchrow($result)) - { - $id_ary[] = (int) $row[$field]; - } - $this->db->sql_freeresult($result); - - // retrieve the total result count if needed - if (!$result_count) - { - $sql_found_rows = 'SELECT FOUND_ROWS() as result_count'; - $result = $this->db->sql_query($sql_found_rows); - $result_count = (int) $this->db->sql_fetchfield('result_count'); - $this->db->sql_freeresult($result); - - if (!$result_count) - { - return false; - } - } - - if ($start >= $result_count) - { - $start = floor(($result_count - 1) / $per_page) * $per_page; - - $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); - while ($row = $this->db->sql_fetchrow($result)) - { - $id_ary[] = (int) $row[$field]; - } - $this->db->sql_freeresult($result); - - $id_ary = array_unique($id_ary); - } - - if (sizeof($id_ary)) - { - $this->save_ids($search_key, '', $author_ary, $result_count, $id_ary, $start, $sort_dir); - $id_ary = array_slice($id_ary, 0, $per_page); - - return $result_count; - } - return false; - } - - /** - * Destroys cached search results, that contained one of the new words in a post so the results won't be outdated - * - * @param string $mode contains the post mode: edit, post, reply, quote ... - * @param int $post_id contains the post id of the post to index - * @param string $message contains the post text of the post - * @param string $subject contains the subject of the post to index - * @param int $poster_id contains the user id of the poster - * @param int $forum_id contains the forum id of parent forum of the post - */ - public function index($mode, $post_id, &$message, &$subject, $poster_id, $forum_id) - { - // Split old and new post/subject to obtain array of words - $split_text = $this->split_message($message); - $split_title = ($subject) ? $this->split_message($subject) : array(); - - $words = array_unique(array_merge($split_text, $split_title)); - - unset($split_text); - unset($split_title); - - // destroy cached search results containing any of the words removed or added - $this->destroy_cache($words, array($poster_id)); - - unset($words); - } - - /** - * Destroy cached results, that might be outdated after deleting a post - */ - public function index_remove($post_ids, $author_ids, $forum_ids) - { - $this->destroy_cache(array(), array_unique($author_ids)); - } - - /** - * Destroy old cache entries - */ - public function tidy() - { - // destroy too old cached search results - $this->destroy_cache(array()); - - set_config('search_last_gc', time(), true); - } - - /** - * Create fulltext index - * - * @return string|bool error string is returned incase of errors otherwise false - */ - public function create_index($acp_module, $u_action) - { - // Make sure we can actually use MySQL with fulltext indexes - if ($error = $this->init()) - { - return $error; - } - - if (empty($this->stats)) - { - $this->get_stats(); - } - - $alter = array(); - - if (!isset($this->stats['post_subject'])) - { - if ($this->db->sql_layer == 'mysqli' || version_compare($this->db->sql_server_info(true), '4.1.3', '>=')) - { - $alter[] = 'MODIFY post_subject varchar(255) COLLATE utf8_unicode_ci DEFAULT \'\' NOT NULL'; - } - else - { - $alter[] = 'MODIFY post_subject text NOT NULL'; - } - $alter[] = 'ADD FULLTEXT (post_subject)'; - } - - if (!isset($this->stats['post_text'])) - { - if ($this->db->sql_layer == 'mysqli' || version_compare($this->db->sql_server_info(true), '4.1.3', '>=')) - { - $alter[] = 'MODIFY post_text mediumtext COLLATE utf8_unicode_ci NOT NULL'; - } - else - { - $alter[] = 'MODIFY post_text mediumtext NOT NULL'; - } - $alter[] = 'ADD FULLTEXT (post_text)'; - } - - if (!isset($this->stats['post_content'])) - { - $alter[] = 'ADD FULLTEXT post_content (post_subject, post_text)'; - } - - if (sizeof($alter)) - { - $this->db->sql_query('ALTER TABLE ' . POSTS_TABLE . ' ' . implode(', ', $alter)); - } - - $this->db->sql_query('TRUNCATE TABLE ' . SEARCH_RESULTS_TABLE); - - return false; - } - - /** - * Drop fulltext index - * - * @return string|bool error string is returned incase of errors otherwise false - */ - public function delete_index($acp_module, $u_action) - { - // Make sure we can actually use MySQL with fulltext indexes - if ($error = $this->init()) - { - return $error; - } - - if (empty($this->stats)) - { - $this->get_stats(); - } - - $alter = array(); - - if (isset($this->stats['post_subject'])) - { - $alter[] = 'DROP INDEX post_subject'; - } - - if (isset($this->stats['post_text'])) - { - $alter[] = 'DROP INDEX post_text'; - } - - if (isset($this->stats['post_content'])) - { - $alter[] = 'DROP INDEX post_content'; - } - - if (sizeof($alter)) - { - $this->db->sql_query('ALTER TABLE ' . POSTS_TABLE . ' ' . implode(', ', $alter)); - } - - $this->db->sql_query('TRUNCATE TABLE ' . SEARCH_RESULTS_TABLE); - - return false; - } - - /** - * Returns true if both FULLTEXT indexes exist - */ - public function index_created() - { - if (empty($this->stats)) - { - $this->get_stats(); - } - - return (isset($this->stats['post_text']) && isset($this->stats['post_subject']) && isset($this->stats['post_content'])) ? true : false; - } - - /** - * Returns an associative array containing information about the indexes - */ - public function index_stats() - { - if (empty($this->stats)) - { - $this->get_stats(); - } - - return array( - $this->user->lang['FULLTEXT_MYSQL_TOTAL_POSTS'] => ($this->index_created()) ? $this->stats['total_posts'] : 0, - ); - } - - /** - * Computes the stats and store them in the $this->stats associative array - */ - protected function get_stats() - { - if (strpos($this->db->sql_layer, 'mysql') === false) - { - $this->stats = array(); - return; - } - - $sql = 'SHOW INDEX - FROM ' . POSTS_TABLE; - $result = $this->db->sql_query($sql); - - while ($row = $this->db->sql_fetchrow($result)) - { - // deal with older MySQL versions which didn't use Index_type - $index_type = (isset($row['Index_type'])) ? $row['Index_type'] : $row['Comment']; - - if ($index_type == 'FULLTEXT') - { - if ($row['Key_name'] == 'post_text') - { - $this->stats['post_text'] = $row; - } - else if ($row['Key_name'] == 'post_subject') - { - $this->stats['post_subject'] = $row; - } - else if ($row['Key_name'] == 'post_content') - { - $this->stats['post_content'] = $row; - } - } - } - $this->db->sql_freeresult($result); - - $this->stats['total_posts'] = empty($this->stats) ? 0 : $this->db->get_estimated_row_count(POSTS_TABLE); - } - - /** - * Display a note, that UTF-8 support is not available with certain versions of PHP - * - * @return associative array containing template and config variables - */ - public function acp() - { - $tpl = ' -
-

' . $this->user->lang['FULLTEXT_MYSQL_MIN_SEARCH_CHARS_EXPLAIN'] . '
-
' . $this->config['fulltext_mysql_min_word_len'] . '
-
-
-

' . $this->user->lang['FULLTEXT_MYSQL_MAX_SEARCH_CHARS_EXPLAIN'] . '
-
' . $this->config['fulltext_mysql_max_word_len'] . '
-
- '; - - // These are fields required in the config table - return array( - 'tpl' => $tpl, - 'config' => array() - ); - } -} diff --git a/phpBB/includes/search/fulltext_native.php b/phpBB/includes/search/fulltext_native.php deleted file mode 100644 index 730c3a6c2d..0000000000 --- a/phpBB/includes/search/fulltext_native.php +++ /dev/null @@ -1,1821 +0,0 @@ -phpbb_root_path = $phpbb_root_path; - $this->php_ext = $phpEx; - $this->config = $config; - $this->db = $db; - $this->user = $user; - - $this->word_length = array('min' => $this->config['fulltext_native_min_chars'], 'max' => $this->config['fulltext_native_max_chars']); - - /** - * Load the UTF tools - */ - if (!class_exists('utf_normalizer')) - { - include($this->phpbb_root_path . 'includes/utf/utf_normalizer.' . $this->php_ext); - } - if (!function_exists('utf8_decode_ncr')) - { - include($this->phpbb_root_path . 'includes/utf/utf_tools.' . $this->php_ext); - } - - $error = false; - } - - /** - * Returns the name of this search backend to be displayed to administrators - * - * @return string Name - */ - public function get_name() - { - return 'phpBB Native Fulltext'; - } - - /** - * Returns the search_query - * - * @return string search query - */ - public function get_search_query() - { - return $this->search_query; - } - - /** - * Returns the common_words array - * - * @return array common words that are ignored by search backend - */ - public function get_common_words() - { - return $this->common_words; - } - - /** - * Returns the word_length array - * - * @return array min and max word length for searching - */ - public function get_word_length() - { - return $this->word_length; - } - - /** - * This function fills $this->search_query with the cleaned user search query - * - * If $terms is 'any' then the words will be extracted from the search query - * and combined with | inside brackets. They will afterwards be treated like - * an standard search query. - * - * Then it analyses the query and fills the internal arrays $must_not_contain_ids, - * $must_contain_ids and $must_exclude_one_ids which are later used by keyword_search() - * - * @param string $keywords contains the search query string as entered by the user - * @param string $terms is either 'all' (use search query as entered, default words to 'must be contained in post') - * or 'any' (find all posts containing at least one of the given words) - * @return boolean false if no valid keywords were found and otherwise true - */ - public function split_keywords($keywords, $terms) - { - $tokens = '+-|()*'; - - $keywords = trim($this->cleanup($keywords, $tokens)); - - // allow word|word|word without brackets - if ((strpos($keywords, ' ') === false) && (strpos($keywords, '|') !== false) && (strpos($keywords, '(') === false)) - { - $keywords = '(' . $keywords . ')'; - } - - $open_bracket = $space = false; - for ($i = 0, $n = strlen($keywords); $i < $n; $i++) - { - if ($open_bracket !== false) - { - switch ($keywords[$i]) - { - case ')': - if ($open_bracket + 1 == $i) - { - $keywords[$i - 1] = '|'; - $keywords[$i] = '|'; - } - $open_bracket = false; - break; - case '(': - $keywords[$i] = '|'; - break; - case '+': - case '-': - case ' ': - $keywords[$i] = '|'; - break; - case '*': - if ($i === 0 || ($keywords[$i - 1] !== '*' && strcspn($keywords[$i - 1], $tokens) === 0)) - { - if ($i === $n - 1 || ($keywords[$i + 1] !== '*' && strcspn($keywords[$i + 1], $tokens) === 0)) - { - $keywords = substr($keywords, 0, $i) . substr($keywords, $i + 1); - } - } - break; - } - } - else - { - switch ($keywords[$i]) - { - case ')': - $keywords[$i] = ' '; - break; - case '(': - $open_bracket = $i; - $space = false; - break; - case '|': - $keywords[$i] = ' '; - break; - case '-': - case '+': - $space = $keywords[$i]; - break; - case ' ': - if ($space !== false) - { - $keywords[$i] = $space; - } - break; - default: - $space = false; - } - } - } - - if ($open_bracket) - { - $keywords .= ')'; - } - - $match = array( - '# +#', - '#\|\|+#', - '#(\+|\-)(?:\+|\-)+#', - '#\(\|#', - '#\|\)#', - ); - $replace = array( - ' ', - '|', - '$1', - '(', - ')', - ); - - $keywords = preg_replace($match, $replace, $keywords); - $num_keywords = sizeof(explode(' ', $keywords)); - - // We limit the number of allowed keywords to minimize load on the database - if ($this->config['max_num_search_keywords'] && $num_keywords > $this->config['max_num_search_keywords']) - { - trigger_error($this->user->lang('MAX_NUM_SEARCH_KEYWORDS_REFINE', $this->config['max_num_search_keywords'], $num_keywords)); - } - - // $keywords input format: each word separated by a space, words in a bracket are not separated - - // the user wants to search for any word, convert the search query - if ($terms == 'any') - { - $words = array(); - - preg_match_all('#([^\\s+\\-|()]+)(?:$|[\\s+\\-|()])#u', $keywords, $words); - if (sizeof($words[1])) - { - $keywords = '(' . implode('|', $words[1]) . ')'; - } - } - - // set the search_query which is shown to the user - $this->search_query = $keywords; - - $exact_words = array(); - preg_match_all('#([^\\s+\\-|*()]+)(?:$|[\\s+\\-|()])#u', $keywords, $exact_words); - $exact_words = $exact_words[1]; - - $common_ids = $words = array(); - - if (sizeof($exact_words)) - { - $sql = 'SELECT word_id, word_text, word_common - FROM ' . SEARCH_WORDLIST_TABLE . ' - WHERE ' . $this->db->sql_in_set('word_text', $exact_words) . ' - ORDER BY word_count ASC'; - $result = $this->db->sql_query($sql); - - // store an array of words and ids, remove common words - while ($row = $this->db->sql_fetchrow($result)) - { - if ($row['word_common']) - { - $this->common_words[] = $row['word_text']; - $common_ids[$row['word_text']] = (int) $row['word_id']; - continue; - } - - $words[$row['word_text']] = (int) $row['word_id']; - } - $this->db->sql_freeresult($result); - } - unset($exact_words); - - // now analyse the search query, first split it using the spaces - $query = explode(' ', $keywords); - - $this->must_contain_ids = array(); - $this->must_not_contain_ids = array(); - $this->must_exclude_one_ids = array(); - - $mode = ''; - $ignore_no_id = true; - - foreach ($query as $word) - { - if (empty($word)) - { - continue; - } - - // words which should not be included - if ($word[0] == '-') - { - $word = substr($word, 1); - - // a group of which at least one may not be in the resulting posts - if ($word[0] == '(') - { - $word = array_unique(explode('|', substr($word, 1, -1))); - $mode = 'must_exclude_one'; - } - // one word which should not be in the resulting posts - else - { - $mode = 'must_not_contain'; - } - $ignore_no_id = true; - } - // words which have to be included - else - { - // no prefix is the same as a +prefix - if ($word[0] == '+') - { - $word = substr($word, 1); - } - - // a group of words of which at least one word should be in every resulting post - if ($word[0] == '(') - { - $word = array_unique(explode('|', substr($word, 1, -1))); - } - $ignore_no_id = false; - $mode = 'must_contain'; - } - - if (empty($word)) - { - continue; - } - - // if this is an array of words then retrieve an id for each - if (is_array($word)) - { - $non_common_words = array(); - $id_words = array(); - foreach ($word as $i => $word_part) - { - if (strpos($word_part, '*') !== false) - { - $id_words[] = '\'' . $this->db->sql_escape(str_replace('*', '%', $word_part)) . '\''; - $non_common_words[] = $word_part; - } - else if (isset($words[$word_part])) - { - $id_words[] = $words[$word_part]; - $non_common_words[] = $word_part; - } - else - { - $len = utf8_strlen($word_part); - if ($len < $this->word_length['min'] || $len > $this->word_length['max']) - { - $this->common_words[] = $word_part; - } - } - } - if (sizeof($id_words)) - { - sort($id_words); - if (sizeof($id_words) > 1) - { - $this->{$mode . '_ids'}[] = $id_words; - } - else - { - $mode = ($mode == 'must_exclude_one') ? 'must_not_contain' : $mode; - $this->{$mode . '_ids'}[] = $id_words[0]; - } - } - // throw an error if we shall not ignore unexistant words - else if (!$ignore_no_id && sizeof($non_common_words)) - { - trigger_error(sprintf($user->lang['WORDS_IN_NO_POST'], implode($user->lang['COMMA_SEPARATOR'], $non_common_words))); - } - unset($non_common_words); - } - // else we only need one id - else if (($wildcard = strpos($word, '*') !== false) || isset($words[$word])) - { - if ($wildcard) - { - $len = utf8_strlen(str_replace('*', '', $word)); - if ($len >= $this->word_length['min'] && $len <= $this->word_length['max']) - { - $this->{$mode . '_ids'}[] = '\'' . $this->db->sql_escape(str_replace('*', '%', $word)) . '\''; - } - else - { - $this->common_words[] = $word; - } - } - else - { - $this->{$mode . '_ids'}[] = $words[$word]; - } - } - // throw an error if we shall not ignore unexistant words - else if (!$ignore_no_id) - { - if (!isset($common_ids[$word])) - { - $len = utf8_strlen($word); - if ($len >= $this->word_length['min'] && $len <= $this->word_length['max']) - { - trigger_error(sprintf($this->user->lang['WORD_IN_NO_POST'], $word)); - } - else - { - $this->common_words[] = $word; - } - } - } - else - { - $len = utf8_strlen($word); - if ($len < $this->word_length['min'] || $len > $this->word_length['max']) - { - $this->common_words[] = $word; - } - } - } - - // we can't search for negatives only - if (!sizeof($this->must_contain_ids)) - { - return false; - } - - if (!empty($this->search_query)) - { - return true; - } - return false; - } - - /** - * Performs a search on keywords depending on display specific params. You have to run split_keywords() first - * - * @param string $type contains either posts or topics depending on what should be searched for - * @param string $fields contains either titleonly (topic titles should be searched), msgonly (only message bodies should be searched), firstpost (only subject and body of the first post should be searched) or all (all post bodies and subjects should be searched) - * @param string $terms is either 'all' (use query as entered, words without prefix should default to "have to be in field") or 'any' (ignore search query parts and just return all posts that contain any of the specified words) - * @param array $sort_by_sql contains SQL code for the ORDER BY part of a query - * @param string $sort_key is the key of $sort_by_sql for the selected sorting - * @param string $sort_dir is either a or d representing ASC and DESC - * @param string $sort_days specifies the maximum amount of days a post may be old - * @param array $ex_fid_ary specifies an array of forum ids which should not be searched - * @param string $post_visibility specifies which types of posts the user can view in which forums - * @param int $topic_id is set to 0 or a topic id, if it is not 0 then only posts in this topic should be searched - * @param array $author_ary an array of author ids if the author should be ignored during the search the array is empty - * @param string $author_name specifies the author match, when ANONYMOUS is also a search-match - * @param array &$id_ary passed by reference, to be filled with ids for the page specified by $start and $per_page, should be ordered - * @param int $start indicates the first index of the page - * @param int $per_page number of ids each page is supposed to contain - * @return boolean|int total number of results - */ - public function keyword_search($type, $fields, $terms, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $post_visibility, $topic_id, $author_ary, $author_name, &$id_ary, &$start, $per_page) - { - // No keywords? No posts. - if (empty($this->search_query)) - { - return false; - } - - $must_contain_ids = $this->must_contain_ids; - $must_not_contain_ids = $this->must_not_contain_ids; - $must_exclude_one_ids = $this->must_exclude_one_ids; - - sort($must_contain_ids); - sort($must_not_contain_ids); - sort($must_exclude_one_ids); - - // generate a search_key from all the options to identify the results - $search_key = md5(implode('#', array( - serialize($must_contain_ids), - serialize($must_not_contain_ids), - serialize($must_exclude_one_ids), - $type, - $fields, - $terms, - $sort_days, - $sort_key, - $topic_id, - implode(',', $ex_fid_ary), - $post_visibility, - implode(',', $author_ary), - $author_name, - ))); - - // try reading the results from cache - $total_results = 0; - if ($this->obtain_ids($search_key, $total_results, $id_ary, $start, $per_page, $sort_dir) == SEARCH_RESULT_IN_CACHE) - { - return $total_results; - } - - $id_ary = array(); - - $sql_where = array(); - $group_by = false; - $m_num = 0; - $w_num = 0; - - $sql_array = array( - 'SELECT' => ($type == 'posts') ? 'p.post_id' : 'p.topic_id', - 'FROM' => array( - SEARCH_WORDMATCH_TABLE => array(), - SEARCH_WORDLIST_TABLE => array(), - ), - 'LEFT_JOIN' => array(array( - 'FROM' => array(POSTS_TABLE => 'p'), - 'ON' => 'm0.post_id = p.post_id', - )), - ); - - $title_match = ''; - $left_join_topics = false; - $group_by = true; - // Build some display specific sql strings - switch ($fields) - { - case 'titleonly': - $title_match = 'title_match = 1'; - $group_by = false; - // no break - case 'firstpost': - $left_join_topics = true; - $sql_where[] = 'p.post_id = t.topic_first_post_id'; - break; - - case 'msgonly': - $title_match = 'title_match = 0'; - $group_by = false; - break; - } - - if ($type == 'topics') - { - $left_join_topics = true; - $group_by = true; - } - - /** - * @todo Add a query optimizer (handle stuff like "+(4|3) +4") - */ - - foreach ($this->must_contain_ids as $subquery) - { - if (is_array($subquery)) - { - $group_by = true; - - $word_id_sql = array(); - $word_ids = array(); - foreach ($subquery as $id) - { - if (is_string($id)) - { - $sql_array['LEFT_JOIN'][] = array( - 'FROM' => array(SEARCH_WORDLIST_TABLE => 'w' . $w_num), - 'ON' => "w$w_num.word_text LIKE $id" - ); - $word_ids[] = "w$w_num.word_id"; - - $w_num++; - } - else - { - $word_ids[] = $id; - } - } - - $sql_where[] = $this->db->sql_in_set("m$m_num.word_id", $word_ids); - - unset($word_id_sql); - unset($word_ids); - } - else if (is_string($subquery)) - { - $sql_array['FROM'][SEARCH_WORDLIST_TABLE][] = 'w' . $w_num; - - $sql_where[] = "w$w_num.word_text LIKE $subquery"; - $sql_where[] = "m$m_num.word_id = w$w_num.word_id"; - - $group_by = true; - $w_num++; - } - else - { - $sql_where[] = "m$m_num.word_id = $subquery"; - } - - $sql_array['FROM'][SEARCH_WORDMATCH_TABLE][] = 'm' . $m_num; - - if ($title_match) - { - $sql_where[] = "m$m_num.$title_match"; - } - - if ($m_num != 0) - { - $sql_where[] = "m$m_num.post_id = m0.post_id"; - } - $m_num++; - } - - foreach ($this->must_not_contain_ids as $key => $subquery) - { - if (is_string($subquery)) - { - $sql_array['LEFT_JOIN'][] = array( - 'FROM' => array(SEARCH_WORDLIST_TABLE => 'w' . $w_num), - 'ON' => "w$w_num.word_text LIKE $subquery" - ); - - $this->must_not_contain_ids[$key] = "w$w_num.word_id"; - - $group_by = true; - $w_num++; - } - } - - if (sizeof($this->must_not_contain_ids)) - { - $sql_array['LEFT_JOIN'][] = array( - 'FROM' => array(SEARCH_WORDMATCH_TABLE => 'm' . $m_num), - 'ON' => $this->db->sql_in_set("m$m_num.word_id", $this->must_not_contain_ids) . (($title_match) ? " AND m$m_num.$title_match" : '') . " AND m$m_num.post_id = m0.post_id" - ); - - $sql_where[] = "m$m_num.word_id IS NULL"; - $m_num++; - } - - foreach ($this->must_exclude_one_ids as $ids) - { - $is_null_joins = array(); - foreach ($ids as $id) - { - if (is_string($id)) - { - $sql_array['LEFT_JOIN'][] = array( - 'FROM' => array(SEARCH_WORDLIST_TABLE => 'w' . $w_num), - 'ON' => "w$w_num.word_text LIKE $id" - ); - $id = "w$w_num.word_id"; - - $group_by = true; - $w_num++; - } - - $sql_array['LEFT_JOIN'][] = array( - 'FROM' => array(SEARCH_WORDMATCH_TABLE => 'm' . $m_num), - 'ON' => "m$m_num.word_id = $id AND m$m_num.post_id = m0.post_id" . (($title_match) ? " AND m$m_num.$title_match" : '') - ); - $is_null_joins[] = "m$m_num.word_id IS NULL"; - - $m_num++; - } - $sql_where[] = '(' . implode(' OR ', $is_null_joins) . ')'; - } - - $sql_where[] = $post_visibility; - - if ($topic_id) - { - $sql_where[] = 'p.topic_id = ' . $topic_id; - } - - if (sizeof($author_ary)) - { - if ($author_name) - { - // first one matches post of registered users, second one guests and deleted users - $sql_author = '(' . $this->db->sql_in_set('p.poster_id', array_diff($author_ary, array(ANONYMOUS)), false, true) . ' OR p.post_username ' . $author_name . ')'; - } - else - { - $sql_author = $this->db->sql_in_set('p.poster_id', $author_ary); - } - $sql_where[] = $sql_author; - } - - if (sizeof($ex_fid_ary)) - { - $sql_where[] = $this->db->sql_in_set('p.forum_id', $ex_fid_ary, true); - } - - if ($sort_days) - { - $sql_where[] = 'p.post_time >= ' . (time() - ($sort_days * 86400)); - } - - $sql_array['WHERE'] = implode(' AND ', $sql_where); - - $is_mysql = false; - // if the total result count is not cached yet, retrieve it from the db - if (!$total_results) - { - $sql = ''; - $sql_array_count = $sql_array; - - if ($left_join_topics) - { - $sql_array_count['LEFT_JOIN'][] = array( - 'FROM' => array(TOPICS_TABLE => 't'), - 'ON' => 'p.topic_id = t.topic_id' - ); - } - - switch ($this->db->sql_layer) - { - case 'mysql4': - case 'mysqli': - - // 3.x does not support SQL_CALC_FOUND_ROWS - // $sql_array['SELECT'] = 'SQL_CALC_FOUND_ROWS ' . $sql_array['SELECT']; - $is_mysql = true; - - break; - - case 'sqlite': - $sql_array_count['SELECT'] = ($type == 'posts') ? 'DISTINCT p.post_id' : 'DISTINCT p.topic_id'; - $sql = 'SELECT COUNT(' . (($type == 'posts') ? 'post_id' : 'topic_id') . ') as total_results - FROM (' . $this->db->sql_build_query('SELECT', $sql_array_count) . ')'; - - // no break - - default: - $sql_array_count['SELECT'] = ($type == 'posts') ? 'COUNT(DISTINCT p.post_id) AS total_results' : 'COUNT(DISTINCT p.topic_id) AS total_results'; - $sql = (!$sql) ? $this->db->sql_build_query('SELECT', $sql_array_count) : $sql; - - $result = $this->db->sql_query($sql); - $total_results = (int) $this->db->sql_fetchfield('total_results'); - $this->db->sql_freeresult($result); - - if (!$total_results) - { - return false; - } - break; - } - - unset($sql_array_count, $sql); - } - - // Build sql strings for sorting - $sql_sort = $sort_by_sql[$sort_key] . (($sort_dir == 'a') ? ' ASC' : ' DESC'); - - switch ($sql_sort[0]) - { - case 'u': - $sql_array['FROM'][USERS_TABLE] = 'u'; - $sql_where[] = 'u.user_id = p.poster_id '; - break; - - case 't': - $left_join_topics = true; - break; - - case 'f': - $sql_array['FROM'][FORUMS_TABLE] = 'f'; - $sql_where[] = 'f.forum_id = p.forum_id'; - break; - } - - if ($left_join_topics) - { - $sql_array['LEFT_JOIN'][] = array( - 'FROM' => array(TOPICS_TABLE => 't'), - 'ON' => 'p.topic_id = t.topic_id' - ); - } - - $sql_array['WHERE'] = implode(' AND ', $sql_where); - $sql_array['GROUP_BY'] = ($group_by) ? (($type == 'posts') ? 'p.post_id' : 'p.topic_id') . ', ' . $sort_by_sql[$sort_key] : ''; - $sql_array['ORDER_BY'] = $sql_sort; - - unset($sql_where, $sql_sort, $group_by); - - $sql = $this->db->sql_build_query('SELECT', $sql_array); - $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); - - while ($row = $this->db->sql_fetchrow($result)) - { - $id_ary[] = (int) $row[(($type == 'posts') ? 'post_id' : 'topic_id')]; - } - $this->db->sql_freeresult($result); - - - // if we use mysql and the total result count is not cached yet, retrieve it from the db - if (!$total_results && $is_mysql) - { - // Count rows for the executed queries. Replace $select within $sql with SQL_CALC_FOUND_ROWS, and run it - $sql_array_copy = $sql_array; - $sql_array_copy['SELECT'] = 'SQL_CALC_FOUND_ROWS p.post_id '; - - $sql_calc = $this->db->sql_build_query('SELECT', $sql_array_copy); - unset($sql_array_copy); - - $this->db->sql_query($sql_calc); - $this->db->sql_freeresult($result); - - $sql_count = 'SELECT FOUND_ROWS() as total_results'; - $result = $this->db->sql_query($sql_count); - $total_results = (int) $this->db->sql_fetchfield('total_results'); - $this->db->sql_freeresult($result); - - if (!$total_results) - { - return false; - } - } - - if ($start >= $total_results) - { - $start = floor(($total_results - 1) / $per_page) * $per_page; - - $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); - - while ($row = $this->db->sql_fetchrow($result)) - { - $id_ary[] = (int) $row[(($type == 'posts') ? 'post_id' : 'topic_id')]; - } - $this->db->sql_freeresult($result); - - } - - // store the ids, from start on then delete anything that isn't on the current page because we only need ids for one page - $this->save_ids($search_key, $this->search_query, $author_ary, $total_results, $id_ary, $start, $sort_dir); - $id_ary = array_slice($id_ary, 0, (int) $per_page); - - return $total_results; - } - - /** - * Performs a search on an author's posts without caring about message contents. Depends on display specific params - * - * @param string $type contains either posts or topics depending on what should be searched for - * @param boolean $firstpost_only if true, only topic starting posts will be considered - * @param array $sort_by_sql contains SQL code for the ORDER BY part of a query - * @param string $sort_key is the key of $sort_by_sql for the selected sorting - * @param string $sort_dir is either a or d representing ASC and DESC - * @param string $sort_days specifies the maximum amount of days a post may be old - * @param array $ex_fid_ary specifies an array of forum ids which should not be searched - * @param string $post_visibility specifies which types of posts the user can view in which forums - * @param int $topic_id is set to 0 or a topic id, if it is not 0 then only posts in this topic should be searched - * @param array $author_ary an array of author ids - * @param string $author_name specifies the author match, when ANONYMOUS is also a search-match - * @param array &$id_ary passed by reference, to be filled with ids for the page specified by $start and $per_page, should be ordered - * @param int $start indicates the first index of the page - * @param int $per_page number of ids each page is supposed to contain - * @return boolean|int total number of results - */ - public function author_search($type, $firstpost_only, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $post_visibility, $topic_id, $author_ary, $author_name, &$id_ary, &$start, $per_page) - { - // No author? No posts - if (!sizeof($author_ary)) - { - return 0; - } - - // generate a search_key from all the options to identify the results - $search_key = md5(implode('#', array( - '', - $type, - ($firstpost_only) ? 'firstpost' : '', - '', - '', - $sort_days, - $sort_key, - $topic_id, - implode(',', $ex_fid_ary), - $post_visibility, - implode(',', $author_ary), - $author_name, - ))); - - // try reading the results from cache - $total_results = 0; - if ($this->obtain_ids($search_key, $total_results, $id_ary, $start, $per_page, $sort_dir) == SEARCH_RESULT_IN_CACHE) - { - return $total_results; - } - - $id_ary = array(); - - // Create some display specific sql strings - if ($author_name) - { - // first one matches post of registered users, second one guests and deleted users - $sql_author = '(' . $this->db->sql_in_set('p.poster_id', array_diff($author_ary, array(ANONYMOUS)), false, true) . ' OR p.post_username ' . $author_name . ')'; - } - else - { - $sql_author = $this->db->sql_in_set('p.poster_id', $author_ary); - } - $sql_fora = (sizeof($ex_fid_ary)) ? ' AND ' . $this->db->sql_in_set('p.forum_id', $ex_fid_ary, true) : ''; - $sql_time = ($sort_days) ? ' AND p.post_time >= ' . (time() - ($sort_days * 86400)) : ''; - $sql_topic_id = ($topic_id) ? ' AND p.topic_id = ' . (int) $topic_id : ''; - $sql_firstpost = ($firstpost_only) ? ' AND p.post_id = t.topic_first_post_id' : ''; - $post_visibility = ($post_visibility) ? ' AND ' . $post_visibility : ''; - - // Build sql strings for sorting - $sql_sort = $sort_by_sql[$sort_key] . (($sort_dir == 'a') ? ' ASC' : ' DESC'); - $sql_sort_table = $sql_sort_join = ''; - switch ($sql_sort[0]) - { - case 'u': - $sql_sort_table = USERS_TABLE . ' u, '; - $sql_sort_join = ' AND u.user_id = p.poster_id '; - break; - - case 't': - $sql_sort_table = ($type == 'posts' && !$firstpost_only) ? TOPICS_TABLE . ' t, ' : ''; - $sql_sort_join = ($type == 'posts' && !$firstpost_only) ? ' AND t.topic_id = p.topic_id ' : ''; - break; - - case 'f': - $sql_sort_table = FORUMS_TABLE . ' f, '; - $sql_sort_join = ' AND f.forum_id = p.forum_id '; - break; - } - - $select = ($type == 'posts') ? 'p.post_id' : 't.topic_id'; - $is_mysql = false; - - // If the cache was completely empty count the results - if (!$total_results) - { - switch ($this->db->sql_layer) - { - case 'mysql4': - case 'mysqli': -// $select = 'SQL_CALC_FOUND_ROWS ' . $select; - $is_mysql = true; - break; - - default: - if ($type == 'posts') - { - $sql = 'SELECT COUNT(p.post_id) as total_results - FROM ' . POSTS_TABLE . ' p' . (($firstpost_only) ? ', ' . TOPICS_TABLE . ' t ' : ' ') . " - WHERE $sql_author - $sql_topic_id - $sql_firstpost - $post_visibility - $sql_fora - $sql_time"; - } - else - { - if ($this->db->sql_layer == 'sqlite') - { - $sql = 'SELECT COUNT(topic_id) as total_results - FROM (SELECT DISTINCT t.topic_id'; - } - else - { - $sql = 'SELECT COUNT(DISTINCT t.topic_id) as total_results'; - } - - $sql .= ' FROM ' . TOPICS_TABLE . ' t, ' . POSTS_TABLE . " p - WHERE $sql_author - $sql_topic_id - $sql_firstpost - $post_visibility - $sql_fora - AND t.topic_id = p.topic_id - $sql_time" . (($this->db->sql_layer == 'sqlite') ? ')' : ''); - } - $result = $this->db->sql_query($sql); - - $total_results = (int) $this->db->sql_fetchfield('total_results'); - $this->db->sql_freeresult($result); - - if (!$total_results) - { - return false; - } - break; - } - } - - // Build the query for really selecting the post_ids - if ($type == 'posts') - { - $sql = "SELECT $select - FROM " . $sql_sort_table . POSTS_TABLE . ' p' . (($firstpost_only) ? ', ' . TOPICS_TABLE . ' t' : '') . " - WHERE $sql_author - $sql_topic_id - $sql_firstpost - $post_visibility - $sql_fora - $sql_sort_join - $sql_time - ORDER BY $sql_sort"; - $field = 'post_id'; - } - else - { - $sql = "SELECT $select - FROM " . $sql_sort_table . TOPICS_TABLE . ' t, ' . POSTS_TABLE . " p - WHERE $sql_author - $sql_topic_id - $sql_firstpost - $post_visibility - $sql_fora - AND t.topic_id = p.topic_id - $sql_sort_join - $sql_time - GROUP BY t.topic_id, " . $sort_by_sql[$sort_key] . ' - ORDER BY ' . $sql_sort; - $field = 'topic_id'; - } - - // Only read one block of posts from the db and then cache it - $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); - - while ($row = $this->db->sql_fetchrow($result)) - { - $id_ary[] = (int) $row[$field]; - } - $this->db->sql_freeresult($result); - - if (!$total_results && $is_mysql) - { - // Count rows for the executed queries. Replace $select within $sql with SQL_CALC_FOUND_ROWS, and run it. - $sql_calc = str_replace('SELECT ' . $select, 'SELECT DISTINCT SQL_CALC_FOUND_ROWS p.post_id', $sql); - - $this->db->sql_query($sql_calc); - $this->db->sql_freeresult($result); - - $sql_count = 'SELECT FOUND_ROWS() as total_results'; - $result = $this->db->sql_query($sql_count); - $total_results = (int) $this->db->sql_fetchfield('total_results'); - $this->db->sql_freeresult($result); - - if (!$total_results) - { - return false; - } - } - - if ($start >= $total_results) - { - $start = floor(($total_results - 1) / $per_page) * $per_page; - - $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); - - while ($row = $this->db->sql_fetchrow($result)) - { - $id_ary[] = (int) $row[$field]; - } - $this->db->sql_freeresult($result); - } - - if (sizeof($id_ary)) - { - $this->save_ids($search_key, '', $author_ary, $total_results, $id_ary, $start, $sort_dir); - $id_ary = array_slice($id_ary, 0, $per_page); - - return $total_results; - } - return false; - } - - /** - * Split a text into words of a given length - * - * The text is converted to UTF-8, cleaned up, and split. Then, words that - * conform to the defined length range are returned in an array. - * - * NOTE: duplicates are NOT removed from the return array - * - * @param string $text Text to split, encoded in UTF-8 - * @return array Array of UTF-8 words - */ - public function split_message($text) - { - $match = $words = array(); - - /** - * Taken from the original code - */ - // Do not index code - $match[] = '#\[code(?:=.*?)?(\:?[0-9a-z]{5,})\].*?\[\/code(\:?[0-9a-z]{5,})\]#is'; - // BBcode - $match[] = '#\[\/?[a-z0-9\*\+\-]+(?:=.*?)?(?::[a-z])?(\:?[0-9a-z]{5,})\]#'; - - $min = $this->word_length['min']; - $max = $this->word_length['max']; - - $isset_min = $min - 1; - - /** - * Clean up the string, remove HTML tags, remove BBCodes - */ - $word = strtok($this->cleanup(preg_replace($match, ' ', strip_tags($text)), -1), ' '); - - while (strlen($word)) - { - if (strlen($word) > 255 || strlen($word) <= $isset_min) - { - /** - * Words longer than 255 bytes are ignored. This will have to be - * changed whenever we change the length of search_wordlist.word_text - * - * Words shorter than $isset_min bytes are ignored, too - */ - $word = strtok(' '); - continue; - } - - $len = utf8_strlen($word); - - /** - * Test whether the word is too short to be indexed. - * - * Note that this limit does NOT apply to CJK and Hangul - */ - if ($len < $min) - { - /** - * Note: this could be optimized. If the codepoint is lower than Hangul's range - * we know that it will also be lower than CJK ranges - */ - if ((strncmp($word, UTF8_HANGUL_FIRST, 3) < 0 || strncmp($word, UTF8_HANGUL_LAST, 3) > 0) - && (strncmp($word, UTF8_CJK_FIRST, 3) < 0 || strncmp($word, UTF8_CJK_LAST, 3) > 0) - && (strncmp($word, UTF8_CJK_B_FIRST, 4) < 0 || strncmp($word, UTF8_CJK_B_LAST, 4) > 0)) - { - $word = strtok(' '); - continue; - } - } - - $words[] = $word; - $word = strtok(' '); - } - - return $words; - } - - /** - * Updates wordlist and wordmatch tables when a message is posted or changed - * - * @param string $mode Contains the post mode: edit, post, reply, quote - * @param int $post_id The id of the post which is modified/created - * @param string &$message New or updated post content - * @param string &$subject New or updated post subject - * @param int $poster_id Post author's user id - * @param int $forum_id The id of the forum in which the post is located - */ - public function index($mode, $post_id, &$message, &$subject, $poster_id, $forum_id) - { - if (!$this->config['fulltext_native_load_upd']) - { - /** - * The search indexer is disabled, return - */ - return; - } - - // Split old and new post/subject to obtain array of 'words' - $split_text = $this->split_message($message); - $split_title = $this->split_message($subject); - - $cur_words = array('post' => array(), 'title' => array()); - - $words = array(); - if ($mode == 'edit') - { - $words['add']['post'] = array(); - $words['add']['title'] = array(); - $words['del']['post'] = array(); - $words['del']['title'] = array(); - - $sql = 'SELECT w.word_id, w.word_text, m.title_match - FROM ' . SEARCH_WORDLIST_TABLE . ' w, ' . SEARCH_WORDMATCH_TABLE . " m - WHERE m.post_id = $post_id - AND w.word_id = m.word_id"; - $result = $this->db->sql_query($sql); - - while ($row = $this->db->sql_fetchrow($result)) - { - $which = ($row['title_match']) ? 'title' : 'post'; - $cur_words[$which][$row['word_text']] = $row['word_id']; - } - $this->db->sql_freeresult($result); - - $words['add']['post'] = array_diff($split_text, array_keys($cur_words['post'])); - $words['add']['title'] = array_diff($split_title, array_keys($cur_words['title'])); - $words['del']['post'] = array_diff(array_keys($cur_words['post']), $split_text); - $words['del']['title'] = array_diff(array_keys($cur_words['title']), $split_title); - } - else - { - $words['add']['post'] = $split_text; - $words['add']['title'] = $split_title; - $words['del']['post'] = array(); - $words['del']['title'] = array(); - } - unset($split_text); - unset($split_title); - - // Get unique words from the above arrays - $unique_add_words = array_unique(array_merge($words['add']['post'], $words['add']['title'])); - - // We now have unique arrays of all words to be added and removed and - // individual arrays of added and removed words for text and title. What - // we need to do now is add the new words (if they don't already exist) - // and then add (or remove) matches between the words and this post - if (sizeof($unique_add_words)) - { - $sql = 'SELECT word_id, word_text - FROM ' . SEARCH_WORDLIST_TABLE . ' - WHERE ' . $this->db->sql_in_set('word_text', $unique_add_words); - $result = $this->db->sql_query($sql); - - $word_ids = array(); - while ($row = $this->db->sql_fetchrow($result)) - { - $word_ids[$row['word_text']] = $row['word_id']; - } - $this->db->sql_freeresult($result); - $new_words = array_diff($unique_add_words, array_keys($word_ids)); - - $this->db->sql_transaction('begin'); - if (sizeof($new_words)) - { - $sql_ary = array(); - - foreach ($new_words as $word) - { - $sql_ary[] = array('word_text' => (string) $word, 'word_count' => 0); - } - $this->db->sql_return_on_error(true); - $this->db->sql_multi_insert(SEARCH_WORDLIST_TABLE, $sql_ary); - $this->db->sql_return_on_error(false); - } - unset($new_words, $sql_ary); - } - else - { - $this->db->sql_transaction('begin'); - } - - // now update the search match table, remove links to removed words and add links to new words - foreach ($words['del'] as $word_in => $word_ary) - { - $title_match = ($word_in == 'title') ? 1 : 0; - - if (sizeof($word_ary)) - { - $sql_in = array(); - foreach ($word_ary as $word) - { - $sql_in[] = $cur_words[$word_in][$word]; - } - - $sql = 'DELETE FROM ' . SEARCH_WORDMATCH_TABLE . ' - WHERE ' . $this->db->sql_in_set('word_id', $sql_in) . ' - AND post_id = ' . intval($post_id) . " - AND title_match = $title_match"; - $this->db->sql_query($sql); - - $sql = 'UPDATE ' . SEARCH_WORDLIST_TABLE . ' - SET word_count = word_count - 1 - WHERE ' . $this->db->sql_in_set('word_id', $sql_in) . ' - AND word_count > 0'; - $this->db->sql_query($sql); - - unset($sql_in); - } - } - - $this->db->sql_return_on_error(true); - foreach ($words['add'] as $word_in => $word_ary) - { - $title_match = ($word_in == 'title') ? 1 : 0; - - if (sizeof($word_ary)) - { - $sql = 'INSERT INTO ' . SEARCH_WORDMATCH_TABLE . ' (post_id, word_id, title_match) - SELECT ' . (int) $post_id . ', word_id, ' . (int) $title_match . ' - FROM ' . SEARCH_WORDLIST_TABLE . ' - WHERE ' . $this->db->sql_in_set('word_text', $word_ary); - $this->db->sql_query($sql); - - $sql = 'UPDATE ' . SEARCH_WORDLIST_TABLE . ' - SET word_count = word_count + 1 - WHERE ' . $this->db->sql_in_set('word_text', $word_ary); - $this->db->sql_query($sql); - } - } - $this->db->sql_return_on_error(false); - - $this->db->sql_transaction('commit'); - - // destroy cached search results containing any of the words removed or added - $this->destroy_cache(array_unique(array_merge($words['add']['post'], $words['add']['title'], $words['del']['post'], $words['del']['title'])), array($poster_id)); - - unset($unique_add_words); - unset($words); - unset($cur_words); - } - - /** - * Removes entries from the wordmatch table for the specified post_ids - */ - public function index_remove($post_ids, $author_ids, $forum_ids) - { - if (sizeof($post_ids)) - { - $sql = 'SELECT w.word_id, w.word_text, m.title_match - FROM ' . SEARCH_WORDMATCH_TABLE . ' m, ' . SEARCH_WORDLIST_TABLE . ' w - WHERE ' . $this->db->sql_in_set('m.post_id', $post_ids) . ' - AND w.word_id = m.word_id'; - $result = $this->db->sql_query($sql); - - $message_word_ids = $title_word_ids = $word_texts = array(); - while ($row = $this->db->sql_fetchrow($result)) - { - if ($row['title_match']) - { - $title_word_ids[] = $row['word_id']; - } - else - { - $message_word_ids[] = $row['word_id']; - } - $word_texts[] = $row['word_text']; - } - $this->db->sql_freeresult($result); - - if (sizeof($title_word_ids)) - { - $sql = 'UPDATE ' . SEARCH_WORDLIST_TABLE . ' - SET word_count = word_count - 1 - WHERE ' . $this->db->sql_in_set('word_id', $title_word_ids) . ' - AND word_count > 0'; - $this->db->sql_query($sql); - } - - if (sizeof($message_word_ids)) - { - $sql = 'UPDATE ' . SEARCH_WORDLIST_TABLE . ' - SET word_count = word_count - 1 - WHERE ' . $this->db->sql_in_set('word_id', $message_word_ids) . ' - AND word_count > 0'; - $this->db->sql_query($sql); - } - - unset($title_word_ids); - unset($message_word_ids); - - $sql = 'DELETE FROM ' . SEARCH_WORDMATCH_TABLE . ' - WHERE ' . $this->db->sql_in_set('post_id', $post_ids); - $this->db->sql_query($sql); - } - - $this->destroy_cache(array_unique($word_texts), array_unique($author_ids)); - } - - /** - * Tidy up indexes: Tag 'common words' and remove - * words no longer referenced in the match table - */ - public function tidy() - { - // Is the fulltext indexer disabled? If yes then we need not - // carry on ... it's okay ... I know when I'm not wanted boo hoo - if (!$this->config['fulltext_native_load_upd']) - { - set_config('search_last_gc', time(), true); - return; - } - - $destroy_cache_words = array(); - - // Remove common words - if ($this->config['num_posts'] >= 100 && $this->config['fulltext_native_common_thres']) - { - $common_threshold = ((double) $this->config['fulltext_native_common_thres']) / 100.0; - // First, get the IDs of common words - $sql = 'SELECT word_id, word_text - FROM ' . SEARCH_WORDLIST_TABLE . ' - WHERE word_count > ' . floor($this->config['num_posts'] * $common_threshold) . ' - OR word_common = 1'; - $result = $this->db->sql_query($sql); - - $sql_in = array(); - while ($row = $this->db->sql_fetchrow($result)) - { - $sql_in[] = $row['word_id']; - $destroy_cache_words[] = $row['word_text']; - } - $this->db->sql_freeresult($result); - - if (sizeof($sql_in)) - { - // Flag the words - $sql = 'UPDATE ' . SEARCH_WORDLIST_TABLE . ' - SET word_common = 1 - WHERE ' . $this->db->sql_in_set('word_id', $sql_in); - $this->db->sql_query($sql); - - // by setting search_last_gc to the new time here we make sure that if a user reloads because the - // following query takes too long, he won't run into it again - set_config('search_last_gc', time(), true); - - // Delete the matches - $sql = 'DELETE FROM ' . SEARCH_WORDMATCH_TABLE . ' - WHERE ' . $this->db->sql_in_set('word_id', $sql_in); - $this->db->sql_query($sql); - } - unset($sql_in); - } - - if (sizeof($destroy_cache_words)) - { - // destroy cached search results containing any of the words that are now common or were removed - $this->destroy_cache(array_unique($destroy_cache_words)); - } - - set_config('search_last_gc', time(), true); - } - - /** - * Deletes all words from the index - */ - public function delete_index($acp_module, $u_action) - { - switch ($this->db->sql_layer) - { - case 'sqlite': - case 'firebird': - $this->db->sql_query('DELETE FROM ' . SEARCH_WORDLIST_TABLE); - $this->db->sql_query('DELETE FROM ' . SEARCH_WORDMATCH_TABLE); - $this->db->sql_query('DELETE FROM ' . SEARCH_RESULTS_TABLE); - break; - - default: - $this->db->sql_query('TRUNCATE TABLE ' . SEARCH_WORDLIST_TABLE); - $this->db->sql_query('TRUNCATE TABLE ' . SEARCH_WORDMATCH_TABLE); - $this->db->sql_query('TRUNCATE TABLE ' . SEARCH_RESULTS_TABLE); - break; - } - } - - /** - * Returns true if both FULLTEXT indexes exist - */ - public function index_created() - { - if (!sizeof($this->stats)) - { - $this->get_stats(); - } - - return ($this->stats['total_words'] && $this->stats['total_matches']) ? true : false; - } - - /** - * Returns an associative array containing information about the indexes - */ - public function index_stats() - { - if (!sizeof($this->stats)) - { - $this->get_stats(); - } - - return array( - $this->user->lang['TOTAL_WORDS'] => $this->stats['total_words'], - $this->user->lang['TOTAL_MATCHES'] => $this->stats['total_matches']); - } - - protected function get_stats() - { - $this->stats['total_words'] = $this->db->get_estimated_row_count(SEARCH_WORDLIST_TABLE); - $this->stats['total_matches'] = $this->db->get_estimated_row_count(SEARCH_WORDMATCH_TABLE); - } - - /** - * Clean up a text to remove non-alphanumeric characters - * - * This method receives a UTF-8 string, normalizes and validates it, replaces all - * non-alphanumeric characters with strings then returns the result. - * - * Any number of "allowed chars" can be passed as a UTF-8 string in NFC. - * - * @param string $text Text to split, in UTF-8 (not normalized or sanitized) - * @param string $allowed_chars String of special chars to allow - * @param string $encoding Text encoding - * @return string Cleaned up text, only alphanumeric chars are left - * - * @todo normalizer::cleanup being able to be used? - */ - protected function cleanup($text, $allowed_chars = null, $encoding = 'utf-8') - { - static $conv = array(), $conv_loaded = array(); - $words = $allow = array(); - - // Convert the text to UTF-8 - $encoding = strtolower($encoding); - if ($encoding != 'utf-8') - { - $text = utf8_recode($text, $encoding); - } - - $utf_len_mask = array( - "\xC0" => 2, - "\xD0" => 2, - "\xE0" => 3, - "\xF0" => 4 - ); - - /** - * Replace HTML entities and NCRs - */ - $text = htmlspecialchars_decode(utf8_decode_ncr($text), ENT_QUOTES); - - /** - * Load the UTF-8 normalizer - * - * If we use it more widely, an instance of that class should be held in a - * a global variable instead - */ - utf_normalizer::nfc($text); - - /** - * The first thing we do is: - * - * - convert ASCII-7 letters to lowercase - * - remove the ASCII-7 non-alpha characters - * - remove the bytes that should not appear in a valid UTF-8 string: 0xC0, - * 0xC1 and 0xF5-0xFF - * - * @todo in theory, the third one is already taken care of during normalization and those chars should have been replaced by Unicode replacement chars - */ - $sb_match = "ISTCPAMELRDOJBNHFGVWUQKYXZ\r\n\t!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F\xC0\xC1\xF5\xF6\xF7\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xFF"; - $sb_replace = 'istcpamelrdojbnhfgvwuqkyxz '; - - /** - * This is the list of legal ASCII chars, it is automatically extended - * with ASCII chars from $allowed_chars - */ - $legal_ascii = ' eaisntroludcpmghbfvq10xy2j9kw354867z'; - - /** - * Prepare an array containing the extra chars to allow - */ - if (isset($allowed_chars[0])) - { - $pos = 0; - $len = strlen($allowed_chars); - do - { - $c = $allowed_chars[$pos]; - - if ($c < "\x80") - { - /** - * ASCII char - */ - $sb_pos = strpos($sb_match, $c); - if (is_int($sb_pos)) - { - /** - * Remove the char from $sb_match and its corresponding - * replacement in $sb_replace - */ - $sb_match = substr($sb_match, 0, $sb_pos) . substr($sb_match, $sb_pos + 1); - $sb_replace = substr($sb_replace, 0, $sb_pos) . substr($sb_replace, $sb_pos + 1); - $legal_ascii .= $c; - } - - ++$pos; - } - else - { - /** - * UTF-8 char - */ - $utf_len = $utf_len_mask[$c & "\xF0"]; - $allow[substr($allowed_chars, $pos, $utf_len)] = 1; - $pos += $utf_len; - } - } - while ($pos < $len); - } - - $text = strtr($text, $sb_match, $sb_replace); - $ret = ''; - - $pos = 0; - $len = strlen($text); - - do - { - /** - * Do all consecutive ASCII chars at once - */ - if ($spn = strspn($text, $legal_ascii, $pos)) - { - $ret .= substr($text, $pos, $spn); - $pos += $spn; - } - - if ($pos >= $len) - { - return $ret; - } - - /** - * Capture the UTF char - */ - $utf_len = $utf_len_mask[$text[$pos] & "\xF0"]; - $utf_char = substr($text, $pos, $utf_len); - $pos += $utf_len; - - if (($utf_char >= UTF8_HANGUL_FIRST && $utf_char <= UTF8_HANGUL_LAST) - || ($utf_char >= UTF8_CJK_FIRST && $utf_char <= UTF8_CJK_LAST) - || ($utf_char >= UTF8_CJK_B_FIRST && $utf_char <= UTF8_CJK_B_LAST)) - { - /** - * All characters within these ranges are valid - * - * We separate them with a space in order to index each character - * individually - */ - $ret .= ' ' . $utf_char . ' '; - continue; - } - - if (isset($allow[$utf_char])) - { - /** - * The char is explicitly allowed - */ - $ret .= $utf_char; - continue; - } - - if (isset($conv[$utf_char])) - { - /** - * The char is mapped to something, maybe to itself actually - */ - $ret .= $conv[$utf_char]; - continue; - } - - /** - * The char isn't mapped, but did we load its conversion table? - * - * The search indexer table is split into blocks. The block number of - * each char is equal to its codepoint right-shifted for 11 bits. It - * means that out of the 11, 16 or 21 meaningful bits of a 2-, 3- or - * 4- byte sequence we only keep the leftmost 0, 5 or 10 bits. Thus, - * all UTF chars encoded in 2 bytes are in the same first block. - */ - if (isset($utf_char[2])) - { - if (isset($utf_char[3])) - { - /** - * 1111 0nnn 10nn nnnn 10nx xxxx 10xx xxxx - * 0000 0111 0011 1111 0010 0000 - */ - $idx = ((ord($utf_char[0]) & 0x07) << 7) | ((ord($utf_char[1]) & 0x3F) << 1) | ((ord($utf_char[2]) & 0x20) >> 5); - } - else - { - /** - * 1110 nnnn 10nx xxxx 10xx xxxx - * 0000 0111 0010 0000 - */ - $idx = ((ord($utf_char[0]) & 0x07) << 1) | ((ord($utf_char[1]) & 0x20) >> 5); - } - } - else - { - /** - * 110x xxxx 10xx xxxx - * 0000 0000 0000 0000 - */ - $idx = 0; - } - - /** - * Check if the required conv table has been loaded already - */ - if (!isset($conv_loaded[$idx])) - { - $conv_loaded[$idx] = 1; - $file = $this->phpbb_root_path . 'includes/utf/data/search_indexer_' . $idx . '.' . $this->php_ext; - - if (file_exists($file)) - { - $conv += include($file); - } - } - - if (isset($conv[$utf_char])) - { - $ret .= $conv[$utf_char]; - } - else - { - /** - * We add an entry to the conversion table so that we - * don't have to convert to codepoint and perform the checks - * that are above this block - */ - $conv[$utf_char] = ' '; - $ret .= ' '; - } - } - while (1); - - return $ret; - } - - /** - * Returns a list of options for the ACP to display - */ - public function acp() - { - /** - * if we need any options, copied from fulltext_native for now, will have to be adjusted or removed - */ - - $tpl = ' -
-

' . $this->user->lang['YES_SEARCH_UPDATE_EXPLAIN'] . '
-
-
-
-

' . $this->user->lang['MIN_SEARCH_CHARS_EXPLAIN'] . '
-
-
-
-

' . $this->user->lang['MAX_SEARCH_CHARS_EXPLAIN'] . '
-
-
-
-

' . $this->user->lang['COMMON_WORD_THRESHOLD_EXPLAIN'] . '
-
%
-
- '; - - // These are fields required in the config table - return array( - 'tpl' => $tpl, - 'config' => array('fulltext_native_load_upd' => 'bool', 'fulltext_native_min_chars' => 'integer:0:255', 'fulltext_native_max_chars' => 'integer:0:255', 'fulltext_native_common_thres' => 'double:0:100') - ); - } -} diff --git a/phpBB/includes/search/fulltext_postgres.php b/phpBB/includes/search/fulltext_postgres.php deleted file mode 100644 index 6b4b310f2e..0000000000 --- a/phpBB/includes/search/fulltext_postgres.php +++ /dev/null @@ -1,963 +0,0 @@ -config = $config; - $this->db = $db; - $this->user = $user; - - $this->word_length = array('min' => $this->config['fulltext_postgres_min_word_len'], 'max' => $this->config['fulltext_postgres_max_word_len']); - - if ($this->db->sql_layer == 'postgres') - { - $pgsql_version = explode(',', substr($this->db->sql_server_info(), 10)); - $this->version = trim($pgsql_version[0]); - if (version_compare($this->version, '8.3', '>=')) - { - $this->tsearch_usable = true; - } - } - - /** - * Load the UTF tools - */ - if (!function_exists('utf8_strlen')) - { - include($phpbb_root_path . 'includes/utf/utf_tools.' . $phpEx); - } - - $error = false; - } - - /** - * Returns the name of this search backend to be displayed to administrators - * - * @return string Name - */ - public function get_name() - { - return 'PostgreSQL Fulltext'; - } - - /** - * Returns the search_query - * - * @return string search query - */ - public function get_search_query() - { - return $this->search_query; - } - - /** - * Returns the common_words array - * - * @return array common words that are ignored by search backend - */ - public function get_common_words() - { - return $this->common_words; - } - - /** - * Returns the word_length array - * - * @return array min and max word length for searching - */ - public function get_word_length() - { - return $this->word_length; - } - - /** - * Returns if phrase search is supported or not - * - * @return bool - */ - public function supports_phrase_search() - { - return $this->phrase_search; - } - - /** - * Checks for correct PostgreSQL version and stores min/max word length in the config - * - * @return string|bool Language key of the error/incompatiblity occurred - */ - public function init() - { - if ($this->db->sql_layer != 'postgres') - { - return $this->user->lang['FULLTEXT_POSTGRES_INCOMPATIBLE_DATABASE']; - } - - if (!$this->tsearch_usable) - { - return $this->user->lang['FULLTEXT_POSTGRES_TS_NOT_USABLE']; - } - - return false; - } - - /** - * Splits keywords entered by a user into an array of words stored in $this->split_words - * Stores the tidied search query in $this->search_query - * - * @param string &$keywords Contains the keyword as entered by the user - * @param string $terms is either 'all' or 'any' - * @return bool false if no valid keywords were found and otherwise true - */ - public function split_keywords(&$keywords, $terms) - { - if ($terms == 'all') - { - $match = array('#\sand\s#iu', '#\sor\s#iu', '#\snot\s#iu', '#(^|\s)\+#', '#(^|\s)-#', '#(^|\s)\|#'); - $replace = array(' +', ' |', ' -', ' +', ' -', ' |'); - - $keywords = preg_replace($match, $replace, $keywords); - } - - // Filter out as above - $split_keywords = preg_replace("#[\"\n\r\t]+#", ' ', trim(htmlspecialchars_decode($keywords))); - - // Split words - $split_keywords = preg_replace('#([^\p{L}\p{N}\'*"()])#u', '$1$1', str_replace('\'\'', '\' \'', trim($split_keywords))); - $matches = array(); - preg_match_all('#(?:[^\p{L}\p{N}*"()]|^)([+\-|]?(?:[\p{L}\p{N}*"()]+\'?)*[\p{L}\p{N}*"()])(?:[^\p{L}\p{N}*"()]|$)#u', $split_keywords, $matches); - $this->split_words = $matches[1]; - - foreach ($this->split_words as $i => $word) - { - $clean_word = preg_replace('#^[+\-|"]#', '', $word); - - // check word length - $clean_len = utf8_strlen(str_replace('*', '', $clean_word)); - if (($clean_len < $this->config['fulltext_postgres_min_word_len']) || ($clean_len > $this->config['fulltext_postgres_max_word_len'])) - { - $this->common_words[] = $word; - unset($this->split_words[$i]); - } - } - - if ($terms == 'any') - { - $this->search_query = ''; - $this->tsearch_query = ''; - foreach ($this->split_words as $word) - { - if ((strpos($word, '+') === 0) || (strpos($word, '-') === 0) || (strpos($word, '|') === 0)) - { - $word = substr($word, 1); - } - $this->search_query .= $word . ' '; - $this->tsearch_query .= '|' . $word . ' '; - } - } - else - { - $this->search_query = ''; - $this->tsearch_query = ''; - foreach ($this->split_words as $word) - { - if (strpos($word, '+') === 0) - { - $this->search_query .= $word . ' '; - $this->tsearch_query .= '&' . substr($word, 1) . ' '; - } - elseif (strpos($word, '-') === 0) - { - $this->search_query .= $word . ' '; - $this->tsearch_query .= '&!' . substr($word, 1) . ' '; - } - elseif (strpos($word, '|') === 0) - { - $this->search_query .= $word . ' '; - $this->tsearch_query .= '|' . substr($word, 1) . ' '; - } - else - { - $this->search_query .= '+' . $word . ' '; - $this->tsearch_query .= '&' . $word . ' '; - } - } - } - - $this->tsearch_query = substr($this->tsearch_query, 1); - $this->search_query = utf8_htmlspecialchars($this->search_query); - - if ($this->search_query) - { - $this->split_words = array_values($this->split_words); - sort($this->split_words); - return true; - } - return false; - } - - /** - * Turns text into an array of words - * @param string $text contains post text/subject - */ - public function split_message($text) - { - // Split words - $text = preg_replace('#([^\p{L}\p{N}\'*])#u', '$1$1', str_replace('\'\'', '\' \'', trim($text))); - $matches = array(); - preg_match_all('#(?:[^\p{L}\p{N}*]|^)([+\-|]?(?:[\p{L}\p{N}*]+\'?)*[\p{L}\p{N}*])(?:[^\p{L}\p{N}*]|$)#u', $text, $matches); - $text = $matches[1]; - - // remove too short or too long words - $text = array_values($text); - for ($i = 0, $n = sizeof($text); $i < $n; $i++) - { - $text[$i] = trim($text[$i]); - if (utf8_strlen($text[$i]) < $this->config['fulltext_postgres_min_word_len'] || utf8_strlen($text[$i]) > $this->config['fulltext_postgres_max_word_len']) - { - unset($text[$i]); - } - } - - return array_values($text); - } - - /** - * Performs a search on keywords depending on display specific params. You have to run split_keywords() first - * - * @param string $type contains either posts or topics depending on what should be searched for - * @param string $fields contains either titleonly (topic titles should be searched), msgonly (only message bodies should be searched), firstpost (only subject and body of the first post should be searched) or all (all post bodies and subjects should be searched) - * @param string $terms is either 'all' (use query as entered, words without prefix should default to "have to be in field") or 'any' (ignore search query parts and just return all posts that contain any of the specified words) - * @param array $sort_by_sql contains SQL code for the ORDER BY part of a query - * @param string $sort_key is the key of $sort_by_sql for the selected sorting - * @param string $sort_dir is either a or d representing ASC and DESC - * @param string $sort_days specifies the maximum amount of days a post may be old - * @param array $ex_fid_ary specifies an array of forum ids which should not be searched - * @param string $post_visibility specifies which types of posts the user can view in which forums - * @param int $topic_id is set to 0 or a topic id, if it is not 0 then only posts in this topic should be searched - * @param array $author_ary an array of author ids if the author should be ignored during the search the array is empty - * @param string $author_name specifies the author match, when ANONYMOUS is also a search-match - * @param array &$id_ary passed by reference, to be filled with ids for the page specified by $start and $per_page, should be ordered - * @param int $start indicates the first index of the page - * @param int $per_page number of ids each page is supposed to contain - * @return boolean|int total number of results - */ - public function keyword_search($type, $fields, $terms, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $post_visibility, $topic_id, $author_ary, $author_name, &$id_ary, &$start, $per_page) - { - // No keywords? No posts - if (!$this->search_query) - { - return false; - } - - // When search query contains queries like -foo - if (strpos($this->search_query, '+') === false) - { - return false; - } - - // generate a search_key from all the options to identify the results - $search_key = md5(implode('#', array( - implode(', ', $this->split_words), - $type, - $fields, - $terms, - $sort_days, - $sort_key, - $topic_id, - implode(',', $ex_fid_ary), - $post_visibility, - implode(',', $author_ary) - ))); - - if ($start < 0) - { - $start = 0; - } - - // try reading the results from cache - $result_count = 0; - if ($this->obtain_ids($search_key, $result_count, $id_ary, $start, $per_page, $sort_dir) == SEARCH_RESULT_IN_CACHE) - { - return $result_count; - } - - $id_ary = array(); - - $join_topic = ($type == 'posts') ? false : true; - - // Build sql strings for sorting - $sql_sort = $sort_by_sql[$sort_key] . (($sort_dir == 'a') ? ' ASC' : ' DESC'); - $sql_sort_table = $sql_sort_join = ''; - - switch ($sql_sort[0]) - { - case 'u': - $sql_sort_table = USERS_TABLE . ' u, '; - $sql_sort_join = ($type == 'posts') ? ' AND u.user_id = p.poster_id ' : ' AND u.user_id = t.topic_poster '; - break; - - case 't': - $join_topic = true; - break; - - case 'f': - $sql_sort_table = FORUMS_TABLE . ' f, '; - $sql_sort_join = ' AND f.forum_id = p.forum_id '; - break; - } - - // Build some display specific sql strings - switch ($fields) - { - case 'titleonly': - $sql_match = 'p.post_subject'; - $sql_match_where = ' AND p.post_id = t.topic_first_post_id'; - $join_topic = true; - break; - - case 'msgonly': - $sql_match = 'p.post_text'; - $sql_match_where = ''; - break; - - case 'firstpost': - $sql_match = 'p.post_subject, p.post_text'; - $sql_match_where = ' AND p.post_id = t.topic_first_post_id'; - $join_topic = true; - break; - - default: - $sql_match = 'p.post_subject, p.post_text'; - $sql_match_where = ''; - break; - } - - $sql_select = ($type == 'posts') ? 'p.post_id' : 'DISTINCT t.topic_id'; - $sql_from = ($join_topic) ? TOPICS_TABLE . ' t, ' : ''; - $field = ($type == 'posts') ? 'post_id' : 'topic_id'; - $sql_author = (sizeof($author_ary) == 1) ? ' = ' . $author_ary[0] : 'IN (' . implode(', ', $author_ary) . ')'; - - if (sizeof($author_ary) && $author_name) - { - // first one matches post of registered users, second one guests and deleted users - $sql_author = '(' . $this->db->sql_in_set('p.poster_id', array_diff($author_ary, array(ANONYMOUS)), false, true) . ' OR p.post_username ' . $author_name . ')'; - } - else if (sizeof($author_ary)) - { - $sql_author = ' AND ' . $this->db->sql_in_set('p.poster_id', $author_ary); - } - else - { - $sql_author = ''; - } - - $sql_where_options = $sql_sort_join; - $sql_where_options .= ($topic_id) ? ' AND p.topic_id = ' . $topic_id : ''; - $sql_where_options .= ($join_topic) ? ' AND t.topic_id = p.topic_id' : ''; - $sql_where_options .= (sizeof($ex_fid_ary)) ? ' AND ' . $this->db->sql_in_set('p.forum_id', $ex_fid_ary, true) : ''; - $sql_where_options .= ' AND ' . $post_visibility; - $sql_where_options .= $sql_author; - $sql_where_options .= ($sort_days) ? ' AND p.post_time >= ' . (time() - ($sort_days * 86400)) : ''; - $sql_where_options .= $sql_match_where; - - $tmp_sql_match = array(); - foreach (explode(',', $sql_match) as $sql_match_column) - { - $tmp_sql_match[] = "to_tsvector ('" . $this->db->sql_escape($this->config['fulltext_postgres_ts_name']) . "', " . $sql_match_column . ") @@ to_tsquery ('" . $this->db->sql_escape($this->config['fulltext_postgres_ts_name']) . "', '" . $this->db->sql_escape($this->tsearch_query) . "')"; - } - - $this->db->sql_transaction('begin'); - - $sql_from = "FROM $sql_from$sql_sort_table" . POSTS_TABLE . " p"; - $sql_where = "WHERE (" . implode(' OR ', $tmp_sql_match) . ") - $sql_where_options"; - $sql = "SELECT $sql_select - $sql_from - $sql_where - ORDER BY $sql_sort"; - $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); - - while ($row = $this->db->sql_fetchrow($result)) - { - $id_ary[] = $row[$field]; - } - $this->db->sql_freeresult($result); - - $id_ary = array_unique($id_ary); - - // if the total result count is not cached yet, retrieve it from the db - if (!$result_count) - { - $sql_count = "SELECT COUNT(*) as result_count - $sql_from - $sql_where"; - $result = $this->db->sql_query($sql_count); - $result_count = (int) $this->db->sql_fetchfield('result_count'); - $this->db->sql_freeresult($result); - - if (!$result_count) - { - return false; - } - } - - $this->db->sql_transaction('commit'); - - if ($start >= $result_count) - { - $start = floor(($result_count - 1) / $per_page) * $per_page; - - $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); - - while ($row = $this->db->sql_fetchrow($result)) - { - $id_ary[] = $row[$field]; - } - $this->db->sql_freeresult($result); - - $id_ary = array_unique($id_ary); - } - - // store the ids, from start on then delete anything that isn't on the current page because we only need ids for one page - $this->save_ids($search_key, implode(' ', $this->split_words), $author_ary, $result_count, $id_ary, $start, $sort_dir); - $id_ary = array_slice($id_ary, 0, (int) $per_page); - - return $result_count; - } - - /** - * Performs a search on an author's posts without caring about message contents. Depends on display specific params - * - * @param string $type contains either posts or topics depending on what should be searched for - * @param boolean $firstpost_only if true, only topic starting posts will be considered - * @param array $sort_by_sql contains SQL code for the ORDER BY part of a query - * @param string $sort_key is the key of $sort_by_sql for the selected sorting - * @param string $sort_dir is either a or d representing ASC and DESC - * @param string $sort_days specifies the maximum amount of days a post may be old - * @param array $ex_fid_ary specifies an array of forum ids which should not be searched - * @param string $post_visibility specifies which types of posts the user can view in which forums - * @param int $topic_id is set to 0 or a topic id, if it is not 0 then only posts in this topic should be searched - * @param array $author_ary an array of author ids - * @param string $author_name specifies the author match, when ANONYMOUS is also a search-match - * @param array &$id_ary passed by reference, to be filled with ids for the page specified by $start and $per_page, should be ordered - * @param int $start indicates the first index of the page - * @param int $per_page number of ids each page is supposed to contain - * @return boolean|int total number of results - */ - public function author_search($type, $firstpost_only, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $post_visibility, $topic_id, $author_ary, $author_name, &$id_ary, &$start, $per_page) - { - // No author? No posts - if (!sizeof($author_ary)) - { - return 0; - } - - // generate a search_key from all the options to identify the results - $search_key = md5(implode('#', array( - '', - $type, - ($firstpost_only) ? 'firstpost' : '', - '', - '', - $sort_days, - $sort_key, - $topic_id, - implode(',', $ex_fid_ary), - $post_visibility, - implode(',', $author_ary), - $author_name, - ))); - - if ($start < 0) - { - $start = 0; - } - - // try reading the results from cache - $result_count = 0; - if ($this->obtain_ids($search_key, $result_count, $id_ary, $start, $per_page, $sort_dir) == SEARCH_RESULT_IN_CACHE) - { - return $result_count; - } - - $id_ary = array(); - - // Create some display specific sql strings - if ($author_name) - { - // first one matches post of registered users, second one guests and deleted users - $sql_author = '(' . $this->db->sql_in_set('p.poster_id', array_diff($author_ary, array(ANONYMOUS)), false, true) . ' OR p.post_username ' . $author_name . ')'; - } - else - { - $sql_author = $this->db->sql_in_set('p.poster_id', $author_ary); - } - $sql_fora = (sizeof($ex_fid_ary)) ? ' AND ' . $this->db->sql_in_set('p.forum_id', $ex_fid_ary, true) : ''; - $sql_topic_id = ($topic_id) ? ' AND p.topic_id = ' . (int) $topic_id : ''; - $sql_time = ($sort_days) ? ' AND p.post_time >= ' . (time() - ($sort_days * 86400)) : ''; - $sql_firstpost = ($firstpost_only) ? ' AND p.post_id = t.topic_first_post_id' : ''; - - // Build sql strings for sorting - $sql_sort = $sort_by_sql[$sort_key] . (($sort_dir == 'a') ? ' ASC' : ' DESC'); - $sql_sort_table = $sql_sort_join = ''; - switch ($sql_sort[0]) - { - case 'u': - $sql_sort_table = USERS_TABLE . ' u, '; - $sql_sort_join = ($type == 'posts') ? ' AND u.user_id = p.poster_id ' : ' AND u.user_id = t.topic_poster '; - break; - - case 't': - $sql_sort_table = ($type == 'posts' && !$firstpost_only) ? TOPICS_TABLE . ' t, ' : ''; - $sql_sort_join = ($type == 'posts' && !$firstpost_only) ? ' AND t.topic_id = p.topic_id ' : ''; - break; - - case 'f': - $sql_sort_table = FORUMS_TABLE . ' f, '; - $sql_sort_join = ' AND f.forum_id = p.forum_id '; - break; - } - - $m_approve_fid_sql = ' AND ' . $post_visibility; - - // Build the query for really selecting the post_ids - if ($type == 'posts') - { - $sql = "SELECT p.post_id - FROM " . $sql_sort_table . POSTS_TABLE . ' p' . (($firstpost_only) ? ', ' . TOPICS_TABLE . ' t ' : ' ') . " - WHERE $sql_author - $sql_topic_id - $sql_firstpost - $m_approve_fid_sql - $sql_fora - $sql_sort_join - $sql_time - ORDER BY $sql_sort"; - $field = 'post_id'; - } - else - { - $sql = "SELECT t.topic_id - FROM " . $sql_sort_table . TOPICS_TABLE . ' t, ' . POSTS_TABLE . " p - WHERE $sql_author - $sql_topic_id - $sql_firstpost - $m_approve_fid_sql - $sql_fora - AND t.topic_id = p.topic_id - $sql_sort_join - $sql_time - GROUP BY t.topic_id, $sort_by_sql[$sort_key] - ORDER BY $sql_sort"; - $field = 'topic_id'; - } - - $this->db->sql_transaction('begin'); - - // Only read one block of posts from the db and then cache it - $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); - - while ($row = $this->db->sql_fetchrow($result)) - { - $id_ary[] = $row[$field]; - } - $this->db->sql_freeresult($result); - - // retrieve the total result count if needed - if (!$result_count) - { - if ($type == 'posts') - { - $sql_count = "SELECT COUNT(*) as result_count - FROM " . $sql_sort_table . POSTS_TABLE . ' p' . (($firstpost_only) ? ', ' . TOPICS_TABLE . ' t ' : ' ') . " - WHERE $sql_author - $sql_topic_id - $sql_firstpost - $m_approve_fid_sql - $sql_fora - $sql_sort_join - $sql_time"; - } - else - { - $sql_count = "SELECT COUNT(*) as result_count - FROM " . $sql_sort_table . TOPICS_TABLE . ' t, ' . POSTS_TABLE . " p - WHERE $sql_author - $sql_topic_id - $sql_firstpost - $m_approve_fid_sql - $sql_fora - AND t.topic_id = p.topic_id - $sql_sort_join - $sql_time - GROUP BY t.topic_id, $sort_by_sql[$sort_key]"; - } - - $result = $this->db->sql_query($sql_count); - $result_count = (int) $this->db->sql_fetchfield('result_count'); - - if (!$result_count) - { - return false; - } - } - - $this->db->sql_transaction('commit'); - - if ($start >= $result_count) - { - $start = floor(($result_count - 1) / $per_page) * $per_page; - - $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); - while ($row = $this->db->sql_fetchrow($result)) - { - $id_ary[] = (int) $row[$field]; - } - $this->db->sql_freeresult($result); - - $id_ary = array_unique($id_ary); - } - - if (sizeof($id_ary)) - { - $this->save_ids($search_key, '', $author_ary, $result_count, $id_ary, $start, $sort_dir); - $id_ary = array_slice($id_ary, 0, $per_page); - - return $result_count; - } - return false; - } - - /** - * Destroys cached search results, that contained one of the new words in a post so the results won't be outdated - * - * @param string $mode contains the post mode: edit, post, reply, quote ... - * @param int $post_id contains the post id of the post to index - * @param string $message contains the post text of the post - * @param string $subject contains the subject of the post to index - * @param int $poster_id contains the user id of the poster - * @param int $forum_id contains the forum id of parent forum of the post - */ - public function index($mode, $post_id, &$message, &$subject, $poster_id, $forum_id) - { - // Split old and new post/subject to obtain array of words - $split_text = $this->split_message($message); - $split_title = ($subject) ? $this->split_message($subject) : array(); - - $words = array_unique(array_merge($split_text, $split_title)); - - unset($split_text); - unset($split_title); - - // destroy cached search results containing any of the words removed or added - $this->destroy_cache($words, array($poster_id)); - - unset($words); - } - - /** - * Destroy cached results, that might be outdated after deleting a post - */ - public function index_remove($post_ids, $author_ids, $forum_ids) - { - $this->destroy_cache(array(), $author_ids); - } - - /** - * Destroy old cache entries - */ - public function tidy() - { - // destroy too old cached search results - $this->destroy_cache(array()); - - set_config('search_last_gc', time(), true); - } - - /** - * Create fulltext index - * - * @return string|bool error string is returned incase of errors otherwise false - */ - public function create_index($acp_module, $u_action) - { - // Make sure we can actually use PostgreSQL with fulltext indexes - if ($error = $this->init()) - { - return $error; - } - - if (empty($this->stats)) - { - $this->get_stats(); - } - - if (!isset($this->stats['post_subject'])) - { - $this->db->sql_query("CREATE INDEX " . POSTS_TABLE . "_" . $this->config['fulltext_postgres_ts_name'] . "_post_subject ON " . POSTS_TABLE . " USING gin (to_tsvector ('" . $this->db->sql_escape($this->config['fulltext_postgres_ts_name']) . "', post_subject))"); - } - - if (!isset($this->stats['post_text'])) - { - $this->db->sql_query("CREATE INDEX " . POSTS_TABLE . "_" . $this->config['fulltext_postgres_ts_name'] . "_post_text ON " . POSTS_TABLE . " USING gin (to_tsvector ('" . $this->db->sql_escape($this->config['fulltext_postgres_ts_name']) . "', post_text))"); - } - - $this->db->sql_query('TRUNCATE TABLE ' . SEARCH_RESULTS_TABLE); - - return false; - } - - /** - * Drop fulltext index - * - * @return string|bool error string is returned incase of errors otherwise false - */ - public function delete_index($acp_module, $u_action) - { - // Make sure we can actually use PostgreSQL with fulltext indexes - if ($error = $this->init()) - { - return $error; - } - - if (empty($this->stats)) - { - $this->get_stats(); - } - - if (isset($this->stats['post_subject'])) - { - $this->db->sql_query('DROP INDEX ' . $this->stats['post_subject']['relname']); - } - - if (isset($this->stats['post_text'])) - { - $this->db->sql_query('DROP INDEX ' . $this->stats['post_text']['relname']); - } - - $this->db->sql_query('TRUNCATE TABLE ' . SEARCH_RESULTS_TABLE); - - return false; - } - - /** - * Returns true if both FULLTEXT indexes exist - */ - public function index_created() - { - if (empty($this->stats)) - { - $this->get_stats(); - } - - return (isset($this->stats['post_text']) && isset($this->stats['post_subject'])) ? true : false; - } - - /** - * Returns an associative array containing information about the indexes - */ - public function index_stats() - { - if (empty($this->stats)) - { - $this->get_stats(); - } - - return array( - $this->user->lang['FULLTEXT_POSTGRES_TOTAL_POSTS'] => ($this->index_created()) ? $this->stats['total_posts'] : 0, - ); - } - - /** - * Computes the stats and store them in the $this->stats associative array - */ - protected function get_stats() - { - if ($this->db->sql_layer != 'postgres') - { - $this->stats = array(); - return; - } - - $sql = "SELECT c2.relname, pg_catalog.pg_get_indexdef(i.indexrelid, 0, true) AS indexdef - FROM pg_catalog.pg_class c1, pg_catalog.pg_index i, pg_catalog.pg_class c2 - WHERE c1.relname = '" . POSTS_TABLE . "' - AND pg_catalog.pg_table_is_visible(c1.oid) - AND c1.oid = i.indrelid - AND i.indexrelid = c2.oid"; - $result = $this->db->sql_query($sql); - - while ($row = $this->db->sql_fetchrow($result)) - { - // deal with older PostgreSQL versions which didn't use Index_type - if (strpos($row['indexdef'], 'to_tsvector') !== false) - { - if ($row['relname'] == POSTS_TABLE . '_' . $this->config['fulltext_postgres_ts_name'] . '_post_text' || $row['relname'] == POSTS_TABLE . '_post_text') - { - $this->stats['post_text'] = $row; - } - else if ($row['relname'] == POSTS_TABLE . '_' . $this->config['fulltext_postgres_ts_name'] . '_post_subject' || $row['relname'] == POSTS_TABLE . '_post_subject') - { - $this->stats['post_subject'] = $row; - } - } - } - $this->db->sql_freeresult($result); - - $this->stats['total_posts'] = $this->config['num_posts']; - } - - /** - * Display various options that can be configured for the backend from the acp - * - * @return associative array containing template and config variables - */ - public function acp() - { - $tpl = ' -
-

' . $this->user->lang['FULLTEXT_POSTGRES_VERSION_CHECK_EXPLAIN'] . '
-
' . (($this->tsearch_usable) ? $this->user->lang['YES'] : $this->user->lang['NO']) . ' (PostgreSQL ' . $this->version . ')
-
-
-

' . $this->user->lang['FULLTEXT_POSTGRES_TS_NAME_EXPLAIN'] . '
-
-
-
-

' . $this->user->lang['FULLTEXT_POSTGRES_MIN_WORD_LEN_EXPLAIN'] . '
-
-
-
-

' . $this->user->lang['FULLTEXT_POSTGRES_MAX_WORD_LEN_EXPLAIN'] . '
-
-
- '; - - // These are fields required in the config table - return array( - 'tpl' => $tpl, - 'config' => array('fulltext_postgres_ts_name' => 'string', 'fulltext_postgres_min_word_len' => 'integer:0:255', 'fulltext_postgres_max_word_len' => 'integer:0:255') - ); - } -} diff --git a/phpBB/includes/search/fulltext_sphinx.php b/phpBB/includes/search/fulltext_sphinx.php deleted file mode 100644 index 2f7b236c78..0000000000 --- a/phpBB/includes/search/fulltext_sphinx.php +++ /dev/null @@ -1,917 +0,0 @@ -phpbb_root_path = $phpbb_root_path; - $this->php_ext = $phpEx; - $this->config = $config; - $this->user = $user; - $this->db = $db; - $this->auth = $auth; - - if (!class_exists('phpbb_db_tools')) - { - require($this->phpbb_root_path . 'includes/db/db_tools.' . $this->php_ext); - } - - // Initialize phpbb_db_tools object - $this->db_tools = new phpbb_db_tools($this->db); - - if(!$this->config['fulltext_sphinx_id']) - { - set_config('fulltext_sphinx_id', unique_id()); - } - $this->id = $this->config['fulltext_sphinx_id']; - $this->indexes = 'index_phpbb_' . $this->id . '_delta;index_phpbb_' . $this->id . '_main'; - - if (!class_exists('SphinxClient')) - { - require($this->phpbb_root_path . 'includes/sphinxapi.' . $this->php_ext); - } - - // Initialize sphinx client - $this->sphinx = new SphinxClient(); - - $this->sphinx->SetServer(($this->config['fulltext_sphinx_host'] ? $this->config['fulltext_sphinx_host'] : 'localhost'), ($this->config['fulltext_sphinx_port'] ? (int) $this->config['fulltext_sphinx_port'] : 9312)); - - $error = false; - } - - /** - * Returns the name of this search backend to be displayed to administrators - * - * @return string Name - */ - public function get_name() - { - return 'Sphinx Fulltext'; - } - - /** - * Returns the search_query - * - * @return string search query - */ - public function get_search_query() - { - return $this->search_query; - } - - /** - * Returns false as there is no word_len array - * - * @return false - */ - public function get_word_length() - { - return false; - } - - /** - * Returns an empty array as there are no common_words - * - * @return array common words that are ignored by search backend - */ - public function get_common_words() - { - return array(); - } - - /** - * Checks permissions and paths, if everything is correct it generates the config file - * - * @return string|bool Language key of the error/incompatiblity encountered, or false if successful - */ - public function init() - { - if ($this->db->sql_layer != 'mysql' && $this->db->sql_layer != 'mysql4' && $this->db->sql_layer != 'mysqli' && $this->db->sql_layer != 'postgres') - { - return $this->user->lang['FULLTEXT_SPHINX_WRONG_DATABASE']; - } - - // Move delta to main index each hour - set_config('search_gc', 3600); - - return false; - } - - /** - * Generates content of sphinx.conf - * - * @return bool True if sphinx.conf content is correctly generated, false otherwise - */ - protected function config_generate() - { - // Check if Database is supported by Sphinx - if ($this->db->sql_layer =='mysql' || $this->db->sql_layer == 'mysql4' || $this->db->sql_layer == 'mysqli') - { - $this->dbtype = 'mysql'; - } - else if ($this->db->sql_layer == 'postgres') - { - $this->dbtype = 'pgsql'; - } - else - { - $this->config_file_data = $this->user->lang('FULLTEXT_SPHINX_WRONG_DATABASE'); - return false; - } - - // Check if directory paths have been filled - if (!$this->config['fulltext_sphinx_data_path']) - { - $this->config_file_data = $this->user->lang('FULLTEXT_SPHINX_NO_CONFIG_DATA'); - return false; - } - - include($this->phpbb_root_path . 'config.' . $this->php_ext); - - /* Now that we're sure everything was entered correctly, - generate a config for the index. We use a config value - fulltext_sphinx_id for this, as it should be unique. */ - $config_object = new phpbb_search_sphinx_config($this->config_file_data); - $config_data = array( - 'source source_phpbb_' . $this->id . '_main' => array( - array('type', $this->dbtype . ' # mysql or pgsql'), - // This config value sql_host needs to be changed incase sphinx and sql are on different servers - array('sql_host', $dbhost . ' # SQL server host sphinx connects to'), - array('sql_user', $dbuser), - array('sql_pass', $dbpasswd), - array('sql_db', $dbname), - array('sql_port', $dbport . ' # optional, default is 3306 for mysql and 5432 for pgsql'), - array('sql_query_pre', 'SET NAMES \'utf8\''), - array('sql_query_pre', 'UPDATE ' . SPHINX_TABLE . ' SET max_doc_id = (SELECT MAX(post_id) FROM ' . POSTS_TABLE . ') WHERE counter_id = 1'), - array('sql_query_range', 'SELECT MIN(post_id), MAX(post_id) FROM ' . POSTS_TABLE . ''), - array('sql_range_step', '5000'), - array('sql_query', 'SELECT - p.post_id AS id, - p.forum_id, - p.topic_id, - p.poster_id, - p.post_visibility, - 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 ' . POSTS_TABLE . ' p, ' . TOPICS_TABLE . ' t - WHERE - p.topic_id = t.topic_id - AND p.post_id >= $start AND p.post_id <= $end'), - array('sql_query_post', ''), - array('sql_query_post_index', 'UPDATE ' . SPHINX_TABLE . ' SET max_doc_id = $maxid WHERE counter_id = 1'), - array('sql_query_info', 'SELECT * FROM ' . POSTS_TABLE . ' WHERE post_id = $id'), - array('sql_attr_uint', 'forum_id'), - array('sql_attr_uint', 'topic_id'), - array('sql_attr_uint', 'poster_id'), - array('sql_attr_uint', 'post_visibility'), - array('sql_attr_bool', 'topic_first_post'), - array('sql_attr_bool', 'deleted'), - array('sql_attr_timestamp' , 'post_time'), - array('sql_attr_timestamp' , 'topic_last_post_time'), - array('sql_attr_str2ordinal', 'post_subject'), - ), - 'source source_phpbb_' . $this->id . '_delta : source_phpbb_' . $this->id . '_main' => array( - array('sql_query_pre', ''), - array('sql_query_range', ''), - array('sql_range_step', ''), - array('sql_query', 'SELECT - p.post_id AS id, - p.forum_id, - p.topic_id, - p.poster_id, - p.post_visibility, - 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 ' . POSTS_TABLE . ' p, ' . TOPICS_TABLE . ' t - WHERE - p.topic_id = t.topic_id - AND p.post_id >= ( SELECT max_doc_id FROM ' . SPHINX_TABLE . ' WHERE counter_id=1 )'), - ), - 'index index_phpbb_' . $this->id . '_main' => array( - array('path', $this->config['fulltext_sphinx_data_path'] . 'index_phpbb_' . $this->id . '_main'), - array('source', 'source_phpbb_' . $this->id . '_main'), - array('docinfo', 'extern'), - array('morphology', 'none'), - array('stopwords', ''), - array('min_word_len', '2'), - array('charset_type', 'utf-8'), - array('charset_table', 'U+FF10..U+FF19->0..9, 0..9, U+FF41..U+FF5A->a..z, U+FF21..U+FF3A->a..z, A..Z->a..z, a..z, U+0149, U+017F, U+0138, U+00DF, U+00FF, U+00C0..U+00D6->U+00E0..U+00F6, U+00E0..U+00F6, U+00D8..U+00DE->U+00F8..U+00FE, U+00F8..U+00FE, U+0100->U+0101, U+0101, U+0102->U+0103, U+0103, U+0104->U+0105, U+0105, U+0106->U+0107, U+0107, U+0108->U+0109, U+0109, U+010A->U+010B, U+010B, U+010C->U+010D, U+010D, U+010E->U+010F, U+010F, U+0110->U+0111, U+0111, U+0112->U+0113, U+0113, U+0114->U+0115, U+0115, U+0116->U+0117, U+0117, U+0118->U+0119, U+0119, U+011A->U+011B, U+011B, U+011C->U+011D, U+011D, U+011E->U+011F, U+011F, U+0130->U+0131, U+0131, U+0132->U+0133, U+0133, U+0134->U+0135, U+0135, U+0136->U+0137, U+0137, U+0139->U+013A, U+013A, U+013B->U+013C, U+013C, U+013D->U+013E, U+013E, U+013F->U+0140, U+0140, U+0141->U+0142, U+0142, U+0143->U+0144, U+0144, U+0145->U+0146, U+0146, U+0147->U+0148, U+0148, U+014A->U+014B, U+014B, U+014C->U+014D, U+014D, U+014E->U+014F, U+014F, U+0150->U+0151, U+0151, U+0152->U+0153, U+0153, U+0154->U+0155, U+0155, U+0156->U+0157, U+0157, U+0158->U+0159, U+0159, U+015A->U+015B, U+015B, U+015C->U+015D, U+015D, U+015E->U+015F, U+015F, U+0160->U+0161, U+0161, U+0162->U+0163, U+0163, U+0164->U+0165, U+0165, U+0166->U+0167, U+0167, U+0168->U+0169, U+0169, U+016A->U+016B, U+016B, U+016C->U+016D, U+016D, U+016E->U+016F, U+016F, U+0170->U+0171, U+0171, U+0172->U+0173, U+0173, U+0174->U+0175, U+0175, U+0176->U+0177, U+0177, U+0178->U+00FF, U+00FF, U+0179->U+017A, U+017A, U+017B->U+017C, U+017C, U+017D->U+017E, U+017E, U+0410..U+042F->U+0430..U+044F, U+0430..U+044F, U+4E00..U+9FFF'), - array('min_prefix_len', '0'), - array('min_infix_len', '0'), - ), - 'index index_phpbb_' . $this->id . '_delta : index_phpbb_' . $this->id . '_main' => array( - array('path', $this->config['fulltext_sphinx_data_path'] . 'index_phpbb_' . $this->id . '_delta'), - array('source', 'source_phpbb_' . $this->id . '_delta'), - ), - 'indexer' => array( - array('mem_limit', $this->config['fulltext_sphinx_indexer_mem_limit'] . 'M'), - ), - 'searchd' => array( - array('compat_sphinxql_magics' , '0'), - array('listen' , ($this->config['fulltext_sphinx_host'] ? $this->config['fulltext_sphinx_host'] : 'localhost') . ':' . ($this->config['fulltext_sphinx_port'] ? $this->config['fulltext_sphinx_port'] : '9312')), - array('log', $this->config['fulltext_sphinx_data_path'] . 'log/searchd.log'), - array('query_log', $this->config['fulltext_sphinx_data_path'] . 'log/sphinx-query.log'), - array('read_timeout', '5'), - array('max_children', '30'), - array('pid_file', $this->config['fulltext_sphinx_data_path'] . 'searchd.pid'), - array('max_matches', (string) SPHINX_MAX_MATCHES), - array('binlog_path', $this->config['fulltext_sphinx_data_path']), - ), - ); - - $non_unique = array('sql_query_pre' => true, 'sql_attr_uint' => true, 'sql_attr_timestamp' => true, 'sql_attr_str2ordinal' => true, 'sql_attr_bool' => true); - $delete = array('sql_group_column' => true, 'sql_date_column' => true, 'sql_str2ordinal_column' => true); - foreach ($config_data as $section_name => $section_data) - { - $section = $config_object->get_section_by_name($section_name); - if (!$section) - { - $section = $config_object->add_section($section_name); - } - - foreach ($delete as $key => $void) - { - $section->delete_variables_by_name($key); - } - - foreach ($non_unique as $key => $void) - { - $section->delete_variables_by_name($key); - } - - foreach ($section_data as $entry) - { - $key = $entry[0]; - $value = $entry[1]; - - if (!isset($non_unique[$key])) - { - $variable = $section->get_variable_by_name($key); - if (!$variable) - { - $variable = $section->create_variable($key, $value); - } - else - { - $variable->set_value($value); - } - } - else - { - $variable = $section->create_variable($key, $value); - } - } - } - $this->config_file_data = $config_object->get_data(); - - return true; - } - - /** - * Splits keywords entered by a user into an array of words stored in $this->split_words - * Stores the tidied search query in $this->search_query - * - * @param string $keywords Contains the keyword as entered by the user - * @param string $terms is either 'all' or 'any' - * @return false if no valid keywords were found and otherwise true - */ - public function split_keywords(&$keywords, $terms) - { - if ($terms == 'all') - { - $match = array('#\sand\s#i', '#\sor\s#i', '#\snot\s#i', '#\+#', '#-#', '#\|#', '#@#'); - $replace = array(' & ', ' | ', ' - ', ' +', ' -', ' |', ''); - - $replacements = 0; - $keywords = preg_replace($match, $replace, $keywords); - $this->sphinx->SetMatchMode(SPH_MATCH_EXTENDED); - } - else - { - $this->sphinx->SetMatchMode(SPH_MATCH_ANY); - } - - // Keep quotes and new lines - $keywords = str_replace(array('"', "\n"), array('"', ' '), trim($keywords)); - - if (strlen($keywords) > 0) - { - $this->search_query = str_replace('"', '"', $keywords); - return true; - } - - return false; - } - - /** - * Performs a search on keywords depending on display specific params. You have to run split_keywords() first - * - * @param string $type contains either posts or topics depending on what should be searched for - * @param string $fields contains either titleonly (topic titles should be searched), msgonly (only message bodies should be searched), firstpost (only subject and body of the first post should be searched) or all (all post bodies and subjects should be searched) - * @param string $terms is either 'all' (use query as entered, words without prefix should default to "have to be in field") or 'any' (ignore search query parts and just return all posts that contain any of the specified words) - * @param array $sort_by_sql contains SQL code for the ORDER BY part of a query - * @param string $sort_key is the key of $sort_by_sql for the selected sorting - * @param string $sort_dir is either a or d representing ASC and DESC - * @param string $sort_days specifies the maximum amount of days a post may be old - * @param array $ex_fid_ary specifies an array of forum ids which should not be searched - * @param string $post_visibility specifies which types of posts the user can view in which forums - * @param int $topic_id is set to 0 or a topic id, if it is not 0 then only posts in this topic should be searched - * @param array $author_ary an array of author ids if the author should be ignored during the search the array is empty - * @param string $author_name specifies the author match, when ANONYMOUS is also a search-match - * @param array &$id_ary passed by reference, to be filled with ids for the page specified by $start and $per_page, should be ordered - * @param int $start indicates the first index of the page - * @param int $per_page number of ids each page is supposed to contain - * @return boolean|int total number of results - */ - public function keyword_search($type, $fields, $terms, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $post_visibility, $topic_id, $author_ary, $author_name, &$id_ary, &$start, $per_page) - { - // No keywords? No posts. - if (!strlen($this->search_query) && !sizeof($author_ary)) - { - return false; - } - - $id_ary = array(); - - $join_topic = ($type != 'posts'); - - // Sorting - - if ($type == 'topics') - { - switch ($sort_key) - { - case 'a': - $this->sphinx->SetGroupBy('topic_id', SPH_GROUPBY_ATTR, 'poster_id ' . (($sort_dir == 'a') ? 'ASC' : 'DESC')); - break; - - case 'f': - $this->sphinx->SetGroupBy('topic_id', SPH_GROUPBY_ATTR, 'forum_id ' . (($sort_dir == 'a') ? 'ASC' : 'DESC')); - break; - - case 'i': - - case 's': - $this->sphinx->SetGroupBy('topic_id', SPH_GROUPBY_ATTR, 'post_subject ' . (($sort_dir == 'a') ? 'ASC' : 'DESC')); - break; - - case 't': - - default: - $this->sphinx->SetGroupBy('topic_id', SPH_GROUPBY_ATTR, 'topic_last_post_time ' . (($sort_dir == 'a') ? 'ASC' : 'DESC')); - break; - } - } - else - { - switch ($sort_key) - { - case 'a': - $this->sphinx->SetSortMode(($sort_dir == 'a') ? SPH_SORT_ATTR_ASC : SPH_SORT_ATTR_DESC, 'poster_id'); - break; - - case 'f': - $this->sphinx->SetSortMode(($sort_dir == 'a') ? SPH_SORT_ATTR_ASC : SPH_SORT_ATTR_DESC, 'forum_id'); - break; - - case 'i': - - case 's': - $this->sphinx->SetSortMode(($sort_dir == 'a') ? SPH_SORT_ATTR_ASC : SPH_SORT_ATTR_DESC, 'post_subject'); - break; - - case 't': - - default: - $this->sphinx->SetSortMode(($sort_dir == 'a') ? SPH_SORT_ATTR_ASC : SPH_SORT_ATTR_DESC, 'post_time'); - break; - } - } - - // Most narrow filters first - if ($topic_id) - { - $this->sphinx->SetFilter('topic_id', array($topic_id)); - } - - $search_query_prefix = ''; - - switch ($fields) - { - case 'titleonly': - // Only search the title - if ($terms == 'all') - { - $search_query_prefix = '@title '; - } - // Weight for the title - $this->sphinx->SetFieldWeights(array("title" => 5, "data" => 1)); - // 1 is first_post, 0 is not first post - $this->sphinx->SetFilter('topic_first_post', array(1)); - break; - - case 'msgonly': - // Only search the body - if ($terms == 'all') - { - $search_query_prefix = '@data '; - } - // Weight for the body - $this->sphinx->SetFieldWeights(array("title" => 1, "data" => 5)); - break; - - case 'firstpost': - // More relative weight for the title, also search the body - $this->sphinx->SetFieldWeights(array("title" => 5, "data" => 1)); - // 1 is first_post, 0 is not first post - $this->sphinx->SetFilter('topic_first_post', array(1)); - break; - - default: - // More relative weight for the title, also search the body - $this->sphinx->SetFieldWeights(array("title" => 5, "data" => 1)); - break; - } - - if (sizeof($author_ary)) - { - $this->sphinx->SetFilter('poster_id', $author_ary); - } - - // As this is not simply possible at the moment, we limit the result to approved posts. - // This will make it impossible for moderators to search unapproved and softdeleted posts, - // but at least it will also cause the same for normal users. - $this->sphinx->SetFilter('post_visibility', array(ITEM_APPROVED)); - - if (sizeof($ex_fid_ary)) - { - // All forums that a user is allowed to access - $fid_ary = array_unique(array_intersect(array_keys($this->auth->acl_getf('f_read', true)), array_keys($this->auth->acl_getf('f_search', true)))); - // All forums that the user wants to and can search in - $search_forums = array_diff($fid_ary, $ex_fid_ary); - - if (sizeof($search_forums)) - { - $this->sphinx->SetFilter('forum_id', $search_forums); - } - } - - $this->sphinx->SetFilter('deleted', array(0)); - - $this->sphinx->SetLimits($start, (int) $per_page, SPHINX_MAX_MATCHES); - $result = $this->sphinx->Query($search_query_prefix . str_replace('"', '"', $this->search_query), $this->indexes); - - // Could be connection to localhost:9312 failed (errno=111, - // msg=Connection refused) during rotate, retry if so - $retries = SPHINX_CONNECT_RETRIES; - while (!$result && (strpos($this->sphinx->GetLastError(), "errno=111,") !== false) && $retries--) - { - usleep(SPHINX_CONNECT_WAIT_TIME); - $result = $this->sphinx->Query($search_query_prefix . str_replace('"', '"', $this->search_query), $this->indexes); - } - - if ($this->sphinx->GetLastError()) - { - add_log('critical', 'LOG_SPHINX_ERROR', $this->sphinx->GetLastError()); - if ($this->auth->acl_get('a_')) - { - trigger_error($this->user->lang('SPHINX_SEARCH_FAILED', $this->sphinx->GetLastError())); - } - else - { - trigger_error($this->user->lang('SPHINX_SEARCH_FAILED_LOG')); - } - } - - $result_count = $result['total_found']; - - if ($result_count && $start >= $result_count) - { - $start = floor(($result_count - 1) / $per_page) * $per_page; - - $this->sphinx->SetLimits((int) $start, (int) $per_page, SPHINX_MAX_MATCHES); - $result = $this->sphinx->Query($search_query_prefix . str_replace('"', '"', $this->search_query), $this->indexes); - - // Could be connection to localhost:9312 failed (errno=111, - // msg=Connection refused) during rotate, retry if so - $retries = SPHINX_CONNECT_RETRIES; - while (!$result && (strpos($this->sphinx->GetLastError(), "errno=111,") !== false) && $retries--) - { - usleep(SPHINX_CONNECT_WAIT_TIME); - $result = $this->sphinx->Query($search_query_prefix . str_replace('"', '"', $this->search_query), $this->indexes); - } - } - - $id_ary = array(); - if (isset($result['matches'])) - { - if ($type == 'posts') - { - $id_ary = array_keys($result['matches']); - } - else - { - foreach ($result['matches'] as $key => $value) - { - $id_ary[] = $value['attrs']['topic_id']; - } - } - } - else - { - return false; - } - - $id_ary = array_slice($id_ary, 0, (int) $per_page); - - return $result_count; - } - - /** - * Performs a search on an author's posts without caring about message contents. Depends on display specific params - * - * @param string $type contains either posts or topics depending on what should be searched for - * @param boolean $firstpost_only if true, only topic starting posts will be considered - * @param array $sort_by_sql contains SQL code for the ORDER BY part of a query - * @param string $sort_key is the key of $sort_by_sql for the selected sorting - * @param string $sort_dir is either a or d representing ASC and DESC - * @param string $sort_days specifies the maximum amount of days a post may be old - * @param array $ex_fid_ary specifies an array of forum ids which should not be searched - * @param string $post_visibility specifies which types of posts the user can view in which forums - * @param int $topic_id is set to 0 or a topic id, if it is not 0 then only posts in this topic should be searched - * @param array $author_ary an array of author ids - * @param string $author_name specifies the author match, when ANONYMOUS is also a search-match - * @param array &$id_ary passed by reference, to be filled with ids for the page specified by $start and $per_page, should be ordered - * @param int $start indicates the first index of the page - * @param int $per_page number of ids each page is supposed to contain - * @return boolean|int total number of results - */ - public function author_search($type, $firstpost_only, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $post_visibility, $topic_id, $author_ary, $author_name, &$id_ary, $start, $per_page) - { - $this->search_query = ''; - - $this->sphinx->SetMatchMode(SPH_MATCH_FULLSCAN); - $fields = ($firstpost_only) ? 'firstpost' : 'all'; - $terms = 'all'; - return $this->keyword_search($type, $fields, $terms, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $post_visibility, $topic_id, $author_ary, $author_name, $id_ary, $start, $per_page); - } - - /** - * Updates wordlist and wordmatch tables when a message is posted or changed - * - * @param string $mode Contains the post mode: edit, post, reply, quote - * @param int $post_id The id of the post which is modified/created - * @param string &$message New or updated post content - * @param string &$subject New or updated post subject - * @param int $poster_id Post author's user id - * @param int $forum_id The id of the forum in which the post is located - */ - public function index($mode, $post_id, &$message, &$subject, $poster_id, $forum_id) - { - if ($mode == 'edit') - { - $this->sphinx->UpdateAttributes($this->indexes, array('forum_id', 'poster_id'), array((int)$post_id => array((int)$forum_id, (int)$poster_id))); - } - else if ($mode != 'post' && $post_id) - { - // Update topic_last_post_time for full topic - $sql_array = array( - 'SELECT' => 'p1.post_id', - 'FROM' => array( - POSTS_TABLE => 'p1', - ), - 'LEFT_JOIN' => array(array( - 'FROM' => array( - POSTS_TABLE => 'p2' - ), - 'ON' => 'p1.topic_id = p2.topic_id', - )), - ); - - $sql = $this->db->sql_build_query('SELECT', $sql_array); - $result = $this->db->sql_query($sql); - - $post_updates = array(); - $post_time = time(); - while ($row = $this->db->sql_fetchrow($result)) - { - $post_updates[(int)$row['post_id']] = array($post_time); - } - $this->db->sql_freeresult($result); - - if (sizeof($post_updates)) - { - $this->sphinx->UpdateAttributes($this->indexes, array('topic_last_post_time'), $post_updates); - } - } - } - - /** - * Delete a post from the index after it was deleted - */ - public function index_remove($post_ids, $author_ids, $forum_ids) - { - $values = array(); - foreach ($post_ids as $post_id) - { - $values[$post_id] = array(1); - } - - $this->sphinx->UpdateAttributes($this->indexes, array('deleted'), $values); - } - - /** - * Nothing needs to be destroyed - */ - public function tidy($create = false) - { - set_config('search_last_gc', time(), true); - } - - /** - * Create sphinx table - * - * @return string|bool error string is returned incase of errors otherwise false - */ - public function create_index($acp_module, $u_action) - { - if (!$this->index_created()) - { - $table_data = array( - 'COLUMNS' => array( - 'counter_id' => array('UINT', 0), - 'max_doc_id' => array('UINT', 0), - ), - 'PRIMARY_KEY' => 'counter_id', - ); - $this->db_tools->sql_create_table(SPHINX_TABLE, $table_data); - - $sql = 'TRUNCATE TABLE ' . SPHINX_TABLE; - $this->db->sql_query($sql); - - $data = array( - 'counter_id' => '1', - 'max_doc_id' => '0', - ); - $sql = 'INSERT INTO ' . SPHINX_TABLE . ' ' . $this->db->sql_build_array('INSERT', $data); - $this->db->sql_query($sql); - } - - return false; - } - - /** - * Drop sphinx table - * - * @return string|bool error string is returned incase of errors otherwise false - */ - public function delete_index($acp_module, $u_action) - { - if (!$this->index_created()) - { - return false; - } - - $this->db_tools->sql_table_drop(SPHINX_TABLE); - - return false; - } - - /** - * Returns true if the sphinx table was created - * - * @return bool true if sphinx table was created - */ - public function index_created($allow_new_files = true) - { - $created = false; - - if ($this->db_tools->sql_table_exists(SPHINX_TABLE)) - { - $created = true; - } - - return $created; - } - - /** - * Returns an associative array containing information about the indexes - * - * @return string|bool Language string of error false otherwise - */ - public function index_stats() - { - if (empty($this->stats)) - { - $this->get_stats(); - } - - return array( - $this->user->lang['FULLTEXT_SPHINX_MAIN_POSTS'] => ($this->index_created()) ? $this->stats['main_posts'] : 0, - $this->user->lang['FULLTEXT_SPHINX_DELTA_POSTS'] => ($this->index_created()) ? $this->stats['total_posts'] - $this->stats['main_posts'] : 0, - $this->user->lang['FULLTEXT_MYSQL_TOTAL_POSTS'] => ($this->index_created()) ? $this->stats['total_posts'] : 0, - ); - } - - /** - * Collects stats that can be displayed on the index maintenance page - */ - protected function get_stats() - { - if ($this->index_created()) - { - $sql = 'SELECT COUNT(post_id) as total_posts - FROM ' . POSTS_TABLE; - $result = $this->db->sql_query($sql); - $this->stats['total_posts'] = (int) $this->db->sql_fetchfield('total_posts'); - $this->db->sql_freeresult($result); - - $sql = 'SELECT COUNT(p.post_id) as main_posts - FROM ' . POSTS_TABLE . ' p, ' . SPHINX_TABLE . ' m - WHERE p.post_id <= m.max_doc_id - AND m.counter_id = 1'; - $result = $this->db->sql_query($sql); - $this->stats['main_posts'] = (int) $this->db->sql_fetchfield('main_posts'); - $this->db->sql_freeresult($result); - } - } - - /** - * Returns a list of options for the ACP to display - * - * @return associative array containing template and config variables - */ - public function acp() - { - $config_vars = array( - 'fulltext_sphinx_data_path' => 'string', - 'fulltext_sphinx_host' => 'string', - 'fulltext_sphinx_port' => 'string', - 'fulltext_sphinx_indexer_mem_limit' => 'int', - ); - - $tpl = ' - ' . $this->user->lang['FULLTEXT_SPHINX_CONFIGURE']. ' -
-

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

' . $this->user->lang['FULLTEXT_SPHINX_HOST_EXPLAIN'] . '
-
-
-
-

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

' . $this->user->lang['FULLTEXT_SPHINX_INDEXER_MEM_LIMIT_EXPLAIN'] . '
-
' . $this->user->lang['MIB'] . '
-
-
-

' . $this->user->lang['FULLTEXT_SPHINX_CONFIG_FILE_EXPLAIN'] . '
-
' . (($this->config_generate()) ? '' : $this->config_file_data) . '
-
- '; - - // These are fields required in the config table - return array( - 'tpl' => $tpl, - 'config' => $config_vars - ); - } -} diff --git a/phpBB/includes/search/index.htm b/phpBB/includes/search/index.htm deleted file mode 100644 index ee1f723a7d..0000000000 --- a/phpBB/includes/search/index.htm +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/phpBB/includes/search/sphinx/config.php b/phpBB/includes/search/sphinx/config.php deleted file mode 100644 index f1864f0c8c..0000000000 --- a/phpBB/includes/search/sphinx/config.php +++ /dev/null @@ -1,288 +0,0 @@ -read($config_data); - } - } - - /** - * Get a section object by its name - * - * @param string $name The name of the section that shall be returned - * @return phpbb_search_sphinx_config_section The section object or null if none was found - * - * @access public - */ - function get_section_by_name($name) - { - for ($i = 0, $size = sizeof($this->sections); $i < $size; $i++) - { - // Make sure this is really a section object and not a comment - if (($this->sections[$i] instanceof phpbb_search_sphinx_config_section) && $this->sections[$i]->get_name() == $name) - { - return $this->sections[$i]; - } - } - } - - /** - * Appends a new empty section to the end of the config - * - * @param string $name The name for the new section - * @return phpbb_search_sphinx_config_section The newly created section object - * - * @access public - */ - function add_section($name) - { - $this->sections[] = new phpbb_search_sphinx_config_section($name, ''); - return $this->sections[sizeof($this->sections) - 1]; - } - - /** - * Reads the config file data - * - * @param string $config_data The config file data - * - * @access private - */ - function read($config_data) - { - $this->sections = array(); - - $section = null; - $found_opening_bracket = false; - $in_value = false; - - foreach ($config_data as $i => $line) - { - // If the value of a variable continues to the next line because the line - // break was escaped then we don't trim leading space but treat it as a part of the value - if ($in_value) - { - $line = rtrim($line); - } - else - { - $line = trim($line); - } - - // If we're not inside a section look for one - if (!$section) - { - // Add empty lines and comments as comment objects to the section list - // that way they're not deleted when reassembling the file from the sections - if (!$line || $line[0] == '#') - { - $this->sections[] = new phpbb_search_sphinx_config_comment($config_file[$i]); - continue; - } - else - { - // Otherwise we scan the line reading the section name until we find - // an opening curly bracket or a comment - $section_name = ''; - $section_name_comment = ''; - $found_opening_bracket = false; - for ($j = 0, $length = strlen($line); $j < $length; $j++) - { - if ($line[$j] == '#') - { - $section_name_comment = substr($line, $j); - break; - } - - if ($found_opening_bracket) - { - continue; - } - - if ($line[$j] == '{') - { - $found_opening_bracket = true; - continue; - } - - $section_name .= $line[$j]; - } - - // And then we create the new section object - $section_name = trim($section_name); - $section = new phpbb_search_sphinx_config_section($section_name, $section_name_comment); - } - } - else - { - // If we're looking for variables inside a section - $skip_first = false; - - // If we're not in a value continuing over the line feed - if (!$in_value) - { - // Then add empty lines and comments as comment objects to the variable list - // of this section so they're not deleted on reassembly - if (!$line || $line[0] == '#') - { - $section->add_variable(new phpbb_search_sphinx_config_comment($config_file[$i])); - continue; - } - - // As long as we haven't yet actually found an opening bracket for this section - // we treat everything as comments so it's not deleted either - if (!$found_opening_bracket) - { - if ($line[0] == '{') - { - $skip_first = true; - $line = substr($line, 1); - $found_opening_bracket = true; - } - else - { - $section->add_variable(new phpbb_search_sphinx_config_comment($config_file[$i])); - continue; - } - } - } - - // If we did not find a comment in this line or still add to the previous - // line's value ... - if ($line || $in_value) - { - if (!$in_value) - { - $name = ''; - $value = ''; - $comment = ''; - $found_assignment = false; - } - $in_value = false; - $end_section = false; - - /* ... then we should prase this line char by char: - - first there's the variable name - - then an equal sign - - the variable value - - possibly a backslash before the linefeed in this case we need to continue - parsing the value in the next line - - a # indicating that the rest of the line is a comment - - a closing curly bracket indicating the end of this section*/ - for ($j = 0, $length = strlen($line); $j < $length; $j++) - { - if ($line[$j] == '#') - { - $comment = substr($line, $j); - break; - } - else if ($line[$j] == '}') - { - $comment = substr($line, $j + 1); - $end_section = true; - break; - } - else if (!$found_assignment) - { - if ($line[$j] == '=') - { - $found_assignment = true; - } - else - { - $name .= $line[$j]; - } - } - else - { - if ($line[$j] == '\\' && $j == $length - 1) - { - $value .= "\n"; - $in_value = true; - // Go to the next line and keep processing the value in there - continue 2; - } - $value .= $line[$j]; - } - } - - // If a name and an equal sign were found then we have append a - // new variable object to the section - if ($name && $found_assignment) - { - $section->add_variable(new phpbb_search_sphinx_config_variable(trim($name), trim($value), ($end_section) ? '' : $comment)); - continue; - } - - /* If we found a closing curly bracket this section has been completed - and we can append it to the section list and continue with looking for - the next section */ - if ($end_section) - { - $section->set_end_comment($comment); - $this->sections[] = $section; - $section = null; - continue; - } - } - - // If we did not find anything meaningful up to here, then just treat it - // as a comment - $comment = ($skip_first) ? "\t" . substr(ltrim($config_file[$i]), 1) : $config_file[$i]; - $section->add_variable(new phpbb_search_sphinx_config_comment($comment)); - } - } - - } - - /** - * Returns the config data - * - * @return string $data The config data that is generated - * - * @access public - */ - function get_data() - { - $data = ""; - foreach ($this->sections as $section) - { - $data .= $section->to_string(); - } - - return $data; - } -} diff --git a/phpBB/includes/search/sphinx/config_comment.php b/phpBB/includes/search/sphinx/config_comment.php deleted file mode 100644 index 7f695dbf0c..0000000000 --- a/phpBB/includes/search/sphinx/config_comment.php +++ /dev/null @@ -1,49 +0,0 @@ -exact_string = $exact_string; - } - - /** - * Simply returns the comment as it was created - * - * @return string The exact string that was specified in the constructor - * - * @access public - */ - function to_string() - { - return $this->exact_string; - } -} diff --git a/phpBB/includes/search/sphinx/config_section.php b/phpBB/includes/search/sphinx/config_section.php deleted file mode 100644 index 79c9c8563d..0000000000 --- a/phpBB/includes/search/sphinx/config_section.php +++ /dev/null @@ -1,162 +0,0 @@ -name = $name; - $this->comment = $comment; - $this->end_comment = ''; - } - - /** - * Add a variable object to the list of variables in this section - * - * @param phpbb_search_sphinx_config_variable $variable The variable object - * - * @access public - */ - function add_variable($variable) - { - $this->variables[] = $variable; - } - - /** - * Adds a comment after the closing bracket in the textual representation - * - * @param string $end_comment - * - * @access public - */ - function set_end_comment($end_comment) - { - $this->end_comment = $end_comment; - } - - /** - * Getter for the name of this section - * - * @return string Section's name - * - * @access public - */ - function get_name() - { - return $this->name; - } - - /** - * Get a variable object by its name - * - * @param string $name The name of the variable that shall be returned - * @return phpbb_search_sphinx_config_section The first variable object from this section with the - * given name or null if none was found - * - * @access public - */ - function get_variable_by_name($name) - { - for ($i = 0, $size = sizeof($this->variables); $i < $size; $i++) - { - // Make sure this is a variable object and not a comment - if (($this->variables[$i] instanceof phpbb_search_sphinx_config_variable) && $this->variables[$i]->get_name() == $name) - { - return $this->variables[$i]; - } - } - } - - /** - * Deletes all variables with the given name - * - * @param string $name The name of the variable objects that are supposed to be removed - * - * @access public - */ - function delete_variables_by_name($name) - { - for ($i = 0, $size = sizeof($this->variables); $i < $size; $i++) - { - // Make sure this is a variable object and not a comment - if (($this->variables[$i] instanceof phpbb_search_sphinx_config_variable) && $this->variables[$i]->get_name() == $name) - { - array_splice($this->variables, $i, 1); - $i--; - } - } - } - - /** - * Create a new variable object and append it to the variable list of this section - * - * @param string $name The name for the new variable - * @param string $value The value for the new variable - * @return phpbb_search_sphinx_config_variable Variable object that was created - * - * @access public - */ - function create_variable($name, $value) - { - $this->variables[] = new phpbb_search_sphinx_config_variable($name, $value, ''); - return $this->variables[sizeof($this->variables) - 1]; - } - - /** - * Turns this object into a string which can be written to a config file - * - * @return string Config data in textual form, parsable for sphinx - * - * @access public - */ - function to_string() - { - $content = $this->name . ' ' . $this->comment . "\n{\n"; - - // Make sure we don't get too many newlines after the opening bracket - while (trim($this->variables[0]->to_string()) == '') - { - array_shift($this->variables); - } - - foreach ($this->variables as $variable) - { - $content .= $variable->to_string(); - } - $content .= '}' . $this->end_comment . "\n"; - - return $content; - } -} diff --git a/phpBB/includes/search/sphinx/config_variable.php b/phpBB/includes/search/sphinx/config_variable.php deleted file mode 100644 index 2c1d35a49c..0000000000 --- a/phpBB/includes/search/sphinx/config_variable.php +++ /dev/null @@ -1,80 +0,0 @@ -name = $name; - $this->value = $value; - $this->comment = $comment; - } - - /** - * Getter for the variable's name - * - * @return string The variable object's name - * - * @access public - */ - function get_name() - { - return $this->name; - } - - /** - * Allows changing the variable's value - * - * @param string $value New value for this variable - * - * @access public - */ - function set_value($value) - { - $this->value = $value; - } - - /** - * Turns this object into a string readable by sphinx - * - * @return string Config data in textual form - * - * @access public - */ - function to_string() - { - return "\t" . $this->name . ' = ' . str_replace("\n", " \\\n", $this->value) . ' ' . $this->comment . "\n"; - } -} diff --git a/phpBB/includes/session.php b/phpBB/includes/session.php deleted file mode 100644 index e0585b1523..0000000000 --- a/phpBB/includes/session.php +++ /dev/null @@ -1,1516 +0,0 @@ -server('PHP_SELF')); - $args = explode('&', htmlspecialchars_decode($request->server('QUERY_STRING'))); - - // If we are unable to get the script name we use REQUEST_URI as a failover and note it within the page array for easier support... - if (!$script_name) - { - $script_name = htmlspecialchars_decode($request->server('REQUEST_URI')); - $script_name = (($pos = strpos($script_name, '?')) !== false) ? substr($script_name, 0, $pos) : $script_name; - $page_array['failover'] = 1; - } - - // Replace backslashes and doubled slashes (could happen on some proxy setups) - $script_name = str_replace(array('\\', '//'), '/', $script_name); - - // Now, remove the sid and let us get a clean query string... - $use_args = array(); - - // Since some browser do not encode correctly we need to do this with some "special" characters... - // " -> %22, ' => %27, < -> %3C, > -> %3E - $find = array('"', "'", '<', '>'); - $replace = array('%22', '%27', '%3C', '%3E'); - - foreach ($args as $key => $argument) - { - if (strpos($argument, 'sid=') === 0) - { - continue; - } - - $use_args[] = str_replace($find, $replace, $argument); - } - unset($args); - - // The following examples given are for an request uri of {path to the phpbb directory}/adm/index.php?i=10&b=2 - - // The current query string - $query_string = trim(implode('&', $use_args)); - - // basenamed page name (for example: index.php) - $page_name = (substr($script_name, -1, 1) == '/') ? '' : basename($script_name); - $page_name = urlencode(htmlspecialchars($page_name)); - - // current directory within the phpBB root (for example: adm) - $root_dirs = explode('/', str_replace('\\', '/', phpbb_realpath($root_path))); - $page_dirs = explode('/', str_replace('\\', '/', phpbb_realpath('./'))); - $intersection = array_intersect_assoc($root_dirs, $page_dirs); - - $root_dirs = array_diff_assoc($root_dirs, $intersection); - $page_dirs = array_diff_assoc($page_dirs, $intersection); - - $page_dir = str_repeat('../', sizeof($root_dirs)) . implode('/', $page_dirs); - - if ($page_dir && substr($page_dir, -1, 1) == '/') - { - $page_dir = substr($page_dir, 0, -1); - } - - // Current page from phpBB root (for example: adm/index.php?i=10&b=2) - $page = (($page_dir) ? $page_dir . '/' : '') . $page_name . (($query_string) ? "?$query_string" : ''); - - // The script path from the webroot to the current directory (for example: /phpBB3/adm/) : always prefixed with / and ends in / - $script_path = trim(str_replace('\\', '/', dirname($script_name))); - - // The script path from the webroot to the phpBB root (for example: /phpBB3/) - $script_dirs = explode('/', $script_path); - array_splice($script_dirs, -sizeof($page_dirs)); - $root_script_path = implode('/', $script_dirs) . (sizeof($root_dirs) ? '/' . implode('/', $root_dirs) : ''); - - // We are on the base level (phpBB root == webroot), lets adjust the variables a bit... - if (!$root_script_path) - { - $root_script_path = ($page_dir) ? str_replace($page_dir, '', $script_path) : $script_path; - } - - $script_path .= (substr($script_path, -1, 1) == '/') ? '' : '/'; - $root_script_path .= (substr($root_script_path, -1, 1) == '/') ? '' : '/'; - - $page_array += array( - 'page_name' => $page_name, - 'page_dir' => $page_dir, - - 'query_string' => $query_string, - 'script_path' => str_replace(' ', '%20', htmlspecialchars($script_path)), - 'root_script_path' => str_replace(' ', '%20', htmlspecialchars($root_script_path)), - - 'page' => $page, - 'forum' => request_var('f', 0), - ); - - return $page_array; - } - - /** - * Get valid hostname/port. HTTP_HOST is used, SERVER_NAME if HTTP_HOST not present. - */ - function extract_current_hostname() - { - global $config, $request; - - // Get hostname - $host = htmlspecialchars_decode($request->header('Host', $request->server('SERVER_NAME'))); - - // Should be a string and lowered - $host = (string) strtolower($host); - - // If host is equal the cookie domain or the server name (if config is set), then we assume it is valid - if ((isset($config['cookie_domain']) && $host === $config['cookie_domain']) || (isset($config['server_name']) && $host === $config['server_name'])) - { - return $host; - } - - // Is the host actually a IP? If so, we use the IP... (IPv4) - if (long2ip(ip2long($host)) === $host) - { - return $host; - } - - // Now return the hostname (this also removes any port definition). The http:// is prepended to construct a valid URL, hosts never have a scheme assigned - $host = @parse_url('http://' . $host); - $host = (!empty($host['host'])) ? $host['host'] : ''; - - // Remove any portions not removed by parse_url (#) - $host = str_replace('#', '', $host); - - // If, by any means, the host is now empty, we will use a "best approach" way to guess one - if (empty($host)) - { - if (!empty($config['server_name'])) - { - $host = $config['server_name']; - } - else if (!empty($config['cookie_domain'])) - { - $host = (strpos($config['cookie_domain'], '.') === 0) ? substr($config['cookie_domain'], 1) : $config['cookie_domain']; - } - else - { - // Set to OS hostname or localhost - $host = (function_exists('php_uname')) ? php_uname('n') : 'localhost'; - } - } - - // It may be still no valid host, but for sure only a hostname (we may further expand on the cookie domain... if set) - return $host; - } - - /** - * Start session management - * - * This is where all session activity begins. We gather various pieces of - * information from the client and server. We test to see if a session already - * exists. If it does, fine and dandy. If it doesn't we'll go on to create a - * new one ... pretty logical heh? We also examine the system load (if we're - * running on a system which makes such information readily available) and - * halt if it's above an admin definable limit. - * - * @param bool $update_session_page if true the session page gets updated. - * This can be set to circumvent certain scripts to update the users last visited page. - */ - function session_begin($update_session_page = true) - { - global $phpEx, $SID, $_SID, $_EXTRA_URL, $db, $config, $phpbb_root_path; - global $request, $phpbb_container; - - // Give us some basic information - $this->time_now = time(); - $this->cookie_data = array('u' => 0, 'k' => ''); - $this->update_session_page = $update_session_page; - $this->browser = $request->header('User-Agent'); - $this->referer = $request->header('Referer'); - $this->forwarded_for = $request->header('X-Forwarded-For'); - - $this->host = $this->extract_current_hostname(); - $this->page = $this->extract_current_page($phpbb_root_path); - - // if the forwarded for header shall be checked we have to validate its contents - if ($config['forwarded_for_check']) - { - $this->forwarded_for = preg_replace('# {2,}#', ' ', str_replace(',', ' ', $this->forwarded_for)); - - // split the list of IPs - $ips = explode(' ', $this->forwarded_for); - foreach ($ips as $ip) - { - // check IPv4 first, the IPv6 is hopefully only going to be used very seldomly - if (!empty($ip) && !preg_match(get_preg_expression('ipv4'), $ip) && !preg_match(get_preg_expression('ipv6'), $ip)) - { - // contains invalid data, don't use the forwarded for header - $this->forwarded_for = ''; - break; - } - } - } - else - { - $this->forwarded_for = ''; - } - - if ($request->is_set($config['cookie_name'] . '_sid', phpbb_request_interface::COOKIE) || $request->is_set($config['cookie_name'] . '_u', phpbb_request_interface::COOKIE)) - { - $this->cookie_data['u'] = request_var($config['cookie_name'] . '_u', 0, false, true); - $this->cookie_data['k'] = request_var($config['cookie_name'] . '_k', '', false, true); - $this->session_id = request_var($config['cookie_name'] . '_sid', '', false, true); - - $SID = (defined('NEED_SID')) ? '?sid=' . $this->session_id : '?sid='; - $_SID = (defined('NEED_SID')) ? $this->session_id : ''; - - if (empty($this->session_id)) - { - $this->session_id = $_SID = request_var('sid', ''); - $SID = '?sid=' . $this->session_id; - $this->cookie_data = array('u' => 0, 'k' => ''); - } - } - else - { - $this->session_id = $_SID = request_var('sid', ''); - $SID = '?sid=' . $this->session_id; - } - - $_EXTRA_URL = array(); - - // Why no forwarded_for et al? Well, too easily spoofed. With the results of my recent requests - // it's pretty clear that in the majority of cases you'll at least be left with a proxy/cache ip. - $this->ip = htmlspecialchars_decode($request->server('REMOTE_ADDR')); - $this->ip = preg_replace('# {2,}#', ' ', str_replace(',', ' ', $this->ip)); - - // split the list of IPs - $ips = explode(' ', trim($this->ip)); - - // Default IP if REMOTE_ADDR is invalid - $this->ip = '127.0.0.1'; - - foreach ($ips as $ip) - { - if (function_exists('phpbb_ip_normalise')) - { - // Normalise IP address - $ip = phpbb_ip_normalise($ip); - - if (empty($ip)) - { - // IP address is invalid. - break; - } - - // IP address is valid. - $this->ip = $ip; - - // Skip legacy code. - continue; - } - - if (preg_match(get_preg_expression('ipv4'), $ip)) - { - $this->ip = $ip; - } - else if (preg_match(get_preg_expression('ipv6'), $ip)) - { - // Quick check for IPv4-mapped address in IPv6 - if (stripos($ip, '::ffff:') === 0) - { - $ipv4 = substr($ip, 7); - - if (preg_match(get_preg_expression('ipv4'), $ipv4)) - { - $ip = $ipv4; - } - } - - $this->ip = $ip; - } - else - { - // We want to use the last valid address in the chain - // Leave foreach loop when address is invalid - break; - } - } - - $this->load = false; - - // Load limit check (if applicable) - if ($config['limit_load'] || $config['limit_search_load']) - { - if ((function_exists('sys_getloadavg') && $load = sys_getloadavg()) || ($load = explode(' ', @file_get_contents('/proc/loadavg')))) - { - $this->load = array_slice($load, 0, 1); - $this->load = floatval($this->load[0]); - } - else - { - set_config('limit_load', '0'); - set_config('limit_search_load', '0'); - } - } - - // if no session id is set, redirect to index.php - $session_id = $request->variable('sid', ''); - if (defined('NEED_SID') && (empty($session_id) || $this->session_id !== $session_id)) - { - send_status_line(401, 'Unauthorized'); - redirect(append_sid("{$phpbb_root_path}index.$phpEx")); - } - - // if session id is set - if (!empty($this->session_id)) - { - $sql = 'SELECT u.*, s.* - FROM ' . SESSIONS_TABLE . ' s, ' . USERS_TABLE . " u - WHERE s.session_id = '" . $db->sql_escape($this->session_id) . "' - AND u.user_id = s.session_user_id"; - $result = $db->sql_query($sql); - $this->data = $db->sql_fetchrow($result); - $db->sql_freeresult($result); - - // Did the session exist in the DB? - if (isset($this->data['user_id'])) - { - // Validate IP length according to admin ... enforces an IP - // check on bots if admin requires this -// $quadcheck = ($config['ip_check_bot'] && $this->data['user_type'] & USER_BOT) ? 4 : $config['ip_check']; - - if (strpos($this->ip, ':') !== false && strpos($this->data['session_ip'], ':') !== false) - { - $s_ip = short_ipv6($this->data['session_ip'], $config['ip_check']); - $u_ip = short_ipv6($this->ip, $config['ip_check']); - } - else - { - $s_ip = implode('.', array_slice(explode('.', $this->data['session_ip']), 0, $config['ip_check'])); - $u_ip = implode('.', array_slice(explode('.', $this->ip), 0, $config['ip_check'])); - } - - $s_browser = ($config['browser_check']) ? trim(strtolower(substr($this->data['session_browser'], 0, 149))) : ''; - $u_browser = ($config['browser_check']) ? trim(strtolower(substr($this->browser, 0, 149))) : ''; - - $s_forwarded_for = ($config['forwarded_for_check']) ? substr($this->data['session_forwarded_for'], 0, 254) : ''; - $u_forwarded_for = ($config['forwarded_for_check']) ? substr($this->forwarded_for, 0, 254) : ''; - - // referer checks - // The @ before $config['referer_validation'] suppresses notices present while running the updater - $check_referer_path = (@$config['referer_validation'] == REFERER_VALIDATE_PATH); - $referer_valid = true; - - // we assume HEAD and TRACE to be foul play and thus only whitelist GET - if (@$config['referer_validation'] && strtolower($request->server('REQUEST_METHOD')) !== 'get') - { - $referer_valid = $this->validate_referer($check_referer_path); - } - - if ($u_ip === $s_ip && $s_browser === $u_browser && $s_forwarded_for === $u_forwarded_for && $referer_valid) - { - $session_expired = false; - - // Check whether the session is still valid if we have one - $method = basename(trim($config['auth_method'])); - - $provider = $phpbb_container->get('auth.provider.' . $method); - - if (!($provider instanceof phpbb_auth_provider_interface)) - { - throw new \RuntimeException($provider . ' must implement phpbb_auth_provider_interface'); - } - - $ret = $provider->validate_session($this->data); - if ($ret !== null && !$ret) - { - $session_expired = true; - } - - if (!$session_expired) - { - // Check the session length timeframe if autologin is not enabled. - // Else check the autologin length... and also removing those having autologin enabled but no longer allowed board-wide. - if (!$this->data['session_autologin']) - { - if ($this->data['session_time'] < $this->time_now - ($config['session_length'] + 60)) - { - $session_expired = true; - } - } - else if (!$config['allow_autologin'] || ($config['max_autologin_time'] && $this->data['session_time'] < $this->time_now - (86400 * (int) $config['max_autologin_time']) + 60)) - { - $session_expired = true; - } - } - - if (!$session_expired) - { - // Only update session DB a minute or so after last update or if page changes - if ($this->time_now - $this->data['session_time'] > 60 || ($this->update_session_page && $this->data['session_page'] != $this->page['page'])) - { - $sql_ary = array('session_time' => $this->time_now); - - if ($this->update_session_page) - { - $sql_ary['session_page'] = substr($this->page['page'], 0, 199); - $sql_ary['session_forum_id'] = $this->page['forum']; - } - - $db->sql_return_on_error(true); - - $this->update_session($sql_ary); - - $db->sql_return_on_error(false); - - // If the database is not yet updated, there will be an error due to the session_forum_id - // @todo REMOVE for 3.0.2 - if ($result === false) - { - unset($sql_ary['session_forum_id']); - - $this->update_session($sql_ary); - } - - if ($this->data['user_id'] != ANONYMOUS && !empty($config['new_member_post_limit']) && $this->data['user_new'] && $config['new_member_post_limit'] <= $this->data['user_posts']) - { - $this->leave_newly_registered(); - } - } - - $this->data['is_registered'] = ($this->data['user_id'] != ANONYMOUS && ($this->data['user_type'] == USER_NORMAL || $this->data['user_type'] == USER_FOUNDER)) ? true : false; - $this->data['is_bot'] = (!$this->data['is_registered'] && $this->data['user_id'] != ANONYMOUS) ? true : false; - $this->data['user_lang'] = basename($this->data['user_lang']); - - return true; - } - } - else - { - // Added logging temporarly to help debug bugs... - if (defined('DEBUG') && $this->data['user_id'] != ANONYMOUS) - { - if ($referer_valid) - { - add_log('critical', 'LOG_IP_BROWSER_FORWARDED_CHECK', $u_ip, $s_ip, $u_browser, $s_browser, htmlspecialchars($u_forwarded_for), htmlspecialchars($s_forwarded_for)); - } - else - { - add_log('critical', 'LOG_REFERER_INVALID', $this->referer); - } - } - } - } - } - - // If we reach here then no (valid) session exists. So we'll create a new one - return $this->session_create(); - } - - /** - * Create a new session - * - * If upon trying to start a session we discover there is nothing existing we - * jump here. Additionally this method is called directly during login to regenerate - * the session for the specific user. In this method we carry out a number of tasks; - * garbage collection, (search)bot checking, banned user comparison. Basically - * though this method will result in a new session for a specific user. - */ - function session_create($user_id = false, $set_admin = false, $persist_login = false, $viewonline = true) - { - global $SID, $_SID, $db, $config, $cache, $phpbb_root_path, $phpEx, $phpbb_container; - - $this->data = array(); - - /* Garbage collection ... remove old sessions updating user information - // if necessary. It means (potentially) 11 queries but only infrequently - if ($this->time_now > $config['session_last_gc'] + $config['session_gc']) - { - $this->session_gc(); - }*/ - - // Do we allow autologin on this board? No? Then override anything - // that may be requested here - if (!$config['allow_autologin']) - { - $this->cookie_data['k'] = $persist_login = false; - } - - /** - * Here we do a bot check, oh er saucy! No, not that kind of bot - * check. We loop through the list of bots defined by the admin and - * see if we have any useragent and/or IP matches. If we do, this is a - * bot, act accordingly - */ - $bot = false; - $active_bots = $cache->obtain_bots(); - - foreach ($active_bots as $row) - { - if ($row['bot_agent'] && preg_match('#' . str_replace('\*', '.*?', preg_quote($row['bot_agent'], '#')) . '#i', $this->browser)) - { - $bot = $row['user_id']; - } - - // If ip is supplied, we will make sure the ip is matching too... - if ($row['bot_ip'] && ($bot || !$row['bot_agent'])) - { - // Set bot to false, then we only have to set it to true if it is matching - $bot = false; - - foreach (explode(',', $row['bot_ip']) as $bot_ip) - { - $bot_ip = trim($bot_ip); - - if (!$bot_ip) - { - continue; - } - - if (strpos($this->ip, $bot_ip) === 0) - { - $bot = (int) $row['user_id']; - break; - } - } - } - - if ($bot) - { - break; - } - } - - $method = basename(trim($config['auth_method'])); - - $provider = $phpbb_container->get('auth.provider.' . $method); - $this->data = $provider->autologin(); - - if (sizeof($this->data)) - { - $this->cookie_data['k'] = ''; - $this->cookie_data['u'] = $this->data['user_id']; - } - - // If we're presented with an autologin key we'll join against it. - // Else if we've been passed a user_id we'll grab data based on that - if (isset($this->cookie_data['k']) && $this->cookie_data['k'] && $this->cookie_data['u'] && !sizeof($this->data)) - { - $sql = 'SELECT u.* - FROM ' . USERS_TABLE . ' u, ' . SESSIONS_KEYS_TABLE . ' k - WHERE u.user_id = ' . (int) $this->cookie_data['u'] . ' - AND u.user_type IN (' . USER_NORMAL . ', ' . USER_FOUNDER . ") - AND k.user_id = u.user_id - AND k.key_id = '" . $db->sql_escape(md5($this->cookie_data['k'])) . "'"; - $result = $db->sql_query($sql); - $this->data = $db->sql_fetchrow($result); - $db->sql_freeresult($result); - $bot = false; - } - else if ($user_id !== false && !sizeof($this->data)) - { - $this->cookie_data['k'] = ''; - $this->cookie_data['u'] = $user_id; - - $sql = 'SELECT * - FROM ' . USERS_TABLE . ' - WHERE user_id = ' . (int) $this->cookie_data['u'] . ' - AND user_type IN (' . USER_NORMAL . ', ' . USER_FOUNDER . ')'; - $result = $db->sql_query($sql); - $this->data = $db->sql_fetchrow($result); - $db->sql_freeresult($result); - $bot = false; - } - - // Bot user, if they have a SID in the Request URI we need to get rid of it - // otherwise they'll index this page with the SID, duplicate content oh my! - if ($bot && isset($_GET['sid'])) - { - send_status_line(301, 'Moved Permanently'); - redirect(build_url(array('sid'))); - } - - // If no data was returned one or more of the following occurred: - // Key didn't match one in the DB - // User does not exist - // User is inactive - // User is bot - if (!sizeof($this->data) || !is_array($this->data)) - { - $this->cookie_data['k'] = ''; - $this->cookie_data['u'] = ($bot) ? $bot : ANONYMOUS; - - if (!$bot) - { - $sql = 'SELECT * - FROM ' . USERS_TABLE . ' - WHERE user_id = ' . (int) $this->cookie_data['u']; - } - else - { - // We give bots always the same session if it is not yet expired. - $sql = 'SELECT u.*, s.* - FROM ' . USERS_TABLE . ' u - LEFT JOIN ' . SESSIONS_TABLE . ' s ON (s.session_user_id = u.user_id) - WHERE u.user_id = ' . (int) $bot; - } - - $result = $db->sql_query($sql); - $this->data = $db->sql_fetchrow($result); - $db->sql_freeresult($result); - } - - if ($this->data['user_id'] != ANONYMOUS && !$bot) - { - $this->data['session_last_visit'] = (isset($this->data['session_time']) && $this->data['session_time']) ? $this->data['session_time'] : (($this->data['user_lastvisit']) ? $this->data['user_lastvisit'] : time()); - } - else - { - $this->data['session_last_visit'] = $this->time_now; - } - - // Force user id to be integer... - $this->data['user_id'] = (int) $this->data['user_id']; - - // At this stage we should have a filled data array, defined cookie u and k data. - // data array should contain recent session info if we're a real user and a recent - // session exists in which case session_id will also be set - - // Is user banned? Are they excluded? Won't return on ban, exists within method - if ($this->data['user_type'] != USER_FOUNDER) - { - if (!$config['forwarded_for_check']) - { - $this->check_ban($this->data['user_id'], $this->ip); - } - else - { - $ips = explode(' ', $this->forwarded_for); - $ips[] = $this->ip; - $this->check_ban($this->data['user_id'], $ips); - } - } - - $this->data['is_registered'] = (!$bot && $this->data['user_id'] != ANONYMOUS && ($this->data['user_type'] == USER_NORMAL || $this->data['user_type'] == USER_FOUNDER)) ? true : false; - $this->data['is_bot'] = ($bot) ? true : false; - - // If our friend is a bot, we re-assign a previously assigned session - if ($this->data['is_bot'] && $bot == $this->data['user_id'] && $this->data['session_id']) - { - // Only assign the current session if the ip, browser and forwarded_for match... - if (strpos($this->ip, ':') !== false && strpos($this->data['session_ip'], ':') !== false) - { - $s_ip = short_ipv6($this->data['session_ip'], $config['ip_check']); - $u_ip = short_ipv6($this->ip, $config['ip_check']); - } - else - { - $s_ip = implode('.', array_slice(explode('.', $this->data['session_ip']), 0, $config['ip_check'])); - $u_ip = implode('.', array_slice(explode('.', $this->ip), 0, $config['ip_check'])); - } - - $s_browser = ($config['browser_check']) ? trim(strtolower(substr($this->data['session_browser'], 0, 149))) : ''; - $u_browser = ($config['browser_check']) ? trim(strtolower(substr($this->browser, 0, 149))) : ''; - - $s_forwarded_for = ($config['forwarded_for_check']) ? substr($this->data['session_forwarded_for'], 0, 254) : ''; - $u_forwarded_for = ($config['forwarded_for_check']) ? substr($this->forwarded_for, 0, 254) : ''; - - if ($u_ip === $s_ip && $s_browser === $u_browser && $s_forwarded_for === $u_forwarded_for) - { - $this->session_id = $this->data['session_id']; - - // Only update session DB a minute or so after last update or if page changes - if ($this->time_now - $this->data['session_time'] > 60 || ($this->update_session_page && $this->data['session_page'] != $this->page['page'])) - { - $this->data['session_time'] = $this->data['session_last_visit'] = $this->time_now; - - $sql_ary = array('session_time' => $this->time_now, 'session_last_visit' => $this->time_now, 'session_admin' => 0); - - if ($this->update_session_page) - { - $sql_ary['session_page'] = substr($this->page['page'], 0, 199); - $sql_ary['session_forum_id'] = $this->page['forum']; - } - - $this->update_session($sql_ary); - - // Update the last visit time - $sql = 'UPDATE ' . USERS_TABLE . ' - SET user_lastvisit = ' . (int) $this->data['session_time'] . ' - WHERE user_id = ' . (int) $this->data['user_id']; - $db->sql_query($sql); - } - - $SID = '?sid='; - $_SID = ''; - return true; - } - else - { - // If the ip and browser does not match make sure we only have one bot assigned to one session - $db->sql_query('DELETE FROM ' . SESSIONS_TABLE . ' WHERE session_user_id = ' . $this->data['user_id']); - } - } - - $session_autologin = (($this->cookie_data['k'] || $persist_login) && $this->data['is_registered']) ? true : false; - $set_admin = ($set_admin && $this->data['is_registered']) ? true : false; - - // Create or update the session - $sql_ary = array( - 'session_user_id' => (int) $this->data['user_id'], - 'session_start' => (int) $this->time_now, - 'session_last_visit' => (int) $this->data['session_last_visit'], - 'session_time' => (int) $this->time_now, - 'session_browser' => (string) trim(substr($this->browser, 0, 149)), - 'session_forwarded_for' => (string) $this->forwarded_for, - 'session_ip' => (string) $this->ip, - 'session_autologin' => ($session_autologin) ? 1 : 0, - 'session_admin' => ($set_admin) ? 1 : 0, - 'session_viewonline' => ($viewonline) ? 1 : 0, - ); - - if ($this->update_session_page) - { - $sql_ary['session_page'] = (string) substr($this->page['page'], 0, 199); - $sql_ary['session_forum_id'] = $this->page['forum']; - } - - $db->sql_return_on_error(true); - - $sql = 'DELETE - FROM ' . SESSIONS_TABLE . ' - WHERE session_id = \'' . $db->sql_escape($this->session_id) . '\' - AND session_user_id = ' . ANONYMOUS; - - if (!defined('IN_ERROR_HANDLER') && (!$this->session_id || !$db->sql_query($sql) || !$db->sql_affectedrows())) - { - // Limit new sessions in 1 minute period (if required) - if (empty($this->data['session_time']) && $config['active_sessions']) - { -// $db->sql_return_on_error(false); - - $sql = 'SELECT COUNT(session_id) AS sessions - FROM ' . SESSIONS_TABLE . ' - WHERE session_time >= ' . ($this->time_now - 60); - $result = $db->sql_query($sql); - $row = $db->sql_fetchrow($result); - $db->sql_freeresult($result); - - if ((int) $row['sessions'] > (int) $config['active_sessions']) - { - send_status_line(503, 'Service Unavailable'); - trigger_error('BOARD_UNAVAILABLE'); - } - } - } - - // Since we re-create the session id here, the inserted row must be unique. Therefore, we display potential errors. - // Commented out because it will not allow forums to update correctly -// $db->sql_return_on_error(false); - - // Something quite important: session_page always holds the *last* page visited, except for the *first* visit. - // We are not able to simply have an empty session_page btw, therefore we need to tell phpBB how to detect this special case. - // If the session id is empty, we have a completely new one and will set an "identifier" here. This identifier is able to be checked later. - if (empty($this->data['session_id'])) - { - // This is a temporary variable, only set for the very first visit - $this->data['session_created'] = true; - } - - $this->session_id = $this->data['session_id'] = md5(unique_id()); - - $sql_ary['session_id'] = (string) $this->session_id; - $sql_ary['session_page'] = (string) substr($this->page['page'], 0, 199); - $sql_ary['session_forum_id'] = $this->page['forum']; - - $sql = 'INSERT INTO ' . SESSIONS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary); - $db->sql_query($sql); - - $db->sql_return_on_error(false); - - // Regenerate autologin/persistent login key - if ($session_autologin) - { - $this->set_login_key(); - } - - // refresh data - $SID = '?sid=' . $this->session_id; - $_SID = $this->session_id; - $this->data = array_merge($this->data, $sql_ary); - - if (!$bot) - { - $cookie_expire = $this->time_now + (($config['max_autologin_time']) ? 86400 * (int) $config['max_autologin_time'] : 31536000); - - $this->set_cookie('u', $this->cookie_data['u'], $cookie_expire); - $this->set_cookie('k', $this->cookie_data['k'], $cookie_expire); - $this->set_cookie('sid', $this->session_id, $cookie_expire); - - unset($cookie_expire); - - $sql = 'SELECT COUNT(session_id) AS sessions - FROM ' . SESSIONS_TABLE . ' - WHERE session_user_id = ' . (int) $this->data['user_id'] . ' - AND session_time >= ' . (int) ($this->time_now - (max($config['session_length'], $config['form_token_lifetime']))); - $result = $db->sql_query($sql); - $row = $db->sql_fetchrow($result); - $db->sql_freeresult($result); - - if ((int) $row['sessions'] <= 1 || empty($this->data['user_form_salt'])) - { - $this->data['user_form_salt'] = unique_id(); - // Update the form key - $sql = 'UPDATE ' . USERS_TABLE . ' - SET user_form_salt = \'' . $db->sql_escape($this->data['user_form_salt']) . '\' - WHERE user_id = ' . (int) $this->data['user_id']; - $db->sql_query($sql); - } - } - else - { - $this->data['session_time'] = $this->data['session_last_visit'] = $this->time_now; - - // Update the last visit time - $sql = 'UPDATE ' . USERS_TABLE . ' - SET user_lastvisit = ' . (int) $this->data['session_time'] . ' - WHERE user_id = ' . (int) $this->data['user_id']; - $db->sql_query($sql); - - $SID = '?sid='; - $_SID = ''; - } - - return true; - } - - /** - * Kills a session - * - * This method does what it says on the tin. It will delete a pre-existing session. - * It resets cookie information (destroying any autologin key within that cookie data) - * and update the users information from the relevant session data. It will then - * grab guest user information. - */ - function session_kill($new_session = true) - { - global $SID, $_SID, $db, $config, $phpbb_root_path, $phpEx, $phpbb_container; - - $sql = 'DELETE FROM ' . SESSIONS_TABLE . " - WHERE session_id = '" . $db->sql_escape($this->session_id) . "' - AND session_user_id = " . (int) $this->data['user_id']; - $db->sql_query($sql); - - // Allow connecting logout with external auth method logout - $method = basename(trim($config['auth_method'])); - - $provider = $phpbb_container->get('auth.provider.' . $method); - $provider->logout($this->data, $new_session); - - if ($this->data['user_id'] != ANONYMOUS) - { - // Delete existing session, update last visit info first! - if (!isset($this->data['session_time'])) - { - $this->data['session_time'] = time(); - } - - $sql = 'UPDATE ' . USERS_TABLE . ' - SET user_lastvisit = ' . (int) $this->data['session_time'] . ' - WHERE user_id = ' . (int) $this->data['user_id']; - $db->sql_query($sql); - - if ($this->cookie_data['k']) - { - $sql = 'DELETE FROM ' . SESSIONS_KEYS_TABLE . ' - WHERE user_id = ' . (int) $this->data['user_id'] . " - AND key_id = '" . $db->sql_escape(md5($this->cookie_data['k'])) . "'"; - $db->sql_query($sql); - } - - // Reset the data array - $this->data = array(); - - $sql = 'SELECT * - FROM ' . USERS_TABLE . ' - WHERE user_id = ' . ANONYMOUS; - $result = $db->sql_query($sql); - $this->data = $db->sql_fetchrow($result); - $db->sql_freeresult($result); - } - - $cookie_expire = $this->time_now - 31536000; - $this->set_cookie('u', '', $cookie_expire); - $this->set_cookie('k', '', $cookie_expire); - $this->set_cookie('sid', '', $cookie_expire); - unset($cookie_expire); - - $SID = '?sid='; - $this->session_id = $_SID = ''; - - // To make sure a valid session is created we create one for the anonymous user - if ($new_session) - { - $this->session_create(ANONYMOUS); - } - - return true; - } - - /** - * Session garbage collection - * - * This looks a lot more complex than it really is. Effectively we are - * deleting any sessions older than an admin definable limit. Due to the - * way in which we maintain session data we have to ensure we update user - * data before those sessions are destroyed. In addition this method - * removes autologin key information that is older than an admin defined - * limit. - */ - function session_gc() - { - global $db, $config, $phpbb_root_path, $phpEx; - - $batch_size = 10; - - if (!$this->time_now) - { - $this->time_now = time(); - } - - // Firstly, delete guest sessions - $sql = 'DELETE FROM ' . SESSIONS_TABLE . ' - WHERE session_user_id = ' . ANONYMOUS . ' - AND session_time < ' . (int) ($this->time_now - $config['session_length']); - $db->sql_query($sql); - - // Get expired sessions, only most recent for each user - $sql = 'SELECT session_user_id, session_page, MAX(session_time) AS recent_time - FROM ' . SESSIONS_TABLE . ' - WHERE session_time < ' . ($this->time_now - $config['session_length']) . ' - GROUP BY session_user_id, session_page'; - $result = $db->sql_query_limit($sql, $batch_size); - - $del_user_id = array(); - $del_sessions = 0; - - while ($row = $db->sql_fetchrow($result)) - { - $sql = 'UPDATE ' . USERS_TABLE . ' - SET user_lastvisit = ' . (int) $row['recent_time'] . ", user_lastpage = '" . $db->sql_escape($row['session_page']) . "' - WHERE user_id = " . (int) $row['session_user_id']; - $db->sql_query($sql); - - $del_user_id[] = (int) $row['session_user_id']; - $del_sessions++; - } - $db->sql_freeresult($result); - - if (sizeof($del_user_id)) - { - // Delete expired sessions - $sql = 'DELETE FROM ' . SESSIONS_TABLE . ' - WHERE ' . $db->sql_in_set('session_user_id', $del_user_id) . ' - AND session_time < ' . ($this->time_now - $config['session_length']); - $db->sql_query($sql); - } - - if ($del_sessions < $batch_size) - { - // Less than 10 users, update gc timer ... else we want gc - // called again to delete other sessions - set_config('session_last_gc', $this->time_now, true); - - if ($config['max_autologin_time']) - { - $sql = 'DELETE FROM ' . SESSIONS_KEYS_TABLE . ' - WHERE last_login < ' . (time() - (86400 * (int) $config['max_autologin_time'])); - $db->sql_query($sql); - } - - // only called from CRON; should be a safe workaround until the infrastructure gets going - if (!class_exists('phpbb_captcha_factory', false)) - { - include($phpbb_root_path . "includes/captcha/captcha_factory." . $phpEx); - } - phpbb_captcha_factory::garbage_collect($config['captcha_plugin']); - - $sql = 'DELETE FROM ' . LOGIN_ATTEMPT_TABLE . ' - WHERE attempt_time < ' . (time() - (int) $config['ip_login_limit_time']); - $db->sql_query($sql); - } - - return; - } - - /** - * Sets a cookie - * - * Sets a cookie of the given name with the specified data for the given length of time. If no time is specified, a session cookie will be set. - * - * @param string $name Name of the cookie, will be automatically prefixed with the phpBB cookie name. track becomes [cookie_name]_track then. - * @param string $cookiedata The data to hold within the cookie - * @param int $cookietime The expiration time as UNIX timestamp. If 0 is provided, a session cookie is set. - */ - function set_cookie($name, $cookiedata, $cookietime) - { - global $config; - - $name_data = rawurlencode($config['cookie_name'] . '_' . $name) . '=' . rawurlencode($cookiedata); - $expire = gmdate('D, d-M-Y H:i:s \\G\\M\\T', $cookietime); - $domain = (!$config['cookie_domain'] || $config['cookie_domain'] == 'localhost' || $config['cookie_domain'] == '127.0.0.1') ? '' : '; domain=' . $config['cookie_domain']; - - header('Set-Cookie: ' . $name_data . (($cookietime) ? '; expires=' . $expire : '') . '; path=' . $config['cookie_path'] . $domain . ((!$config['cookie_secure']) ? '' : '; secure') . '; HttpOnly', false); - } - - /** - * Check for banned user - * - * Checks whether the supplied user is banned by id, ip or email. If no parameters - * are passed to the method pre-existing session data is used. If $return is false - * this routine does not return on finding a banned user, it outputs a relevant - * message and stops execution. - * - * @param string|array $user_ips Can contain a string with one IP or an array of multiple IPs - */ - function check_ban($user_id = false, $user_ips = false, $user_email = false, $return = false) - { - global $config, $db; - - if (defined('IN_CHECK_BAN')) - { - return; - } - - $banned = false; - $cache_ttl = 3600; - $where_sql = array(); - - $sql = 'SELECT ban_ip, ban_userid, ban_email, ban_exclude, ban_give_reason, ban_end - FROM ' . BANLIST_TABLE . ' - WHERE '; - - // Determine which entries to check, only return those - if ($user_email === false) - { - $where_sql[] = "ban_email = ''"; - } - - if ($user_ips === false) - { - $where_sql[] = "(ban_ip = '' OR ban_exclude = 1)"; - } - - if ($user_id === false) - { - $where_sql[] = '(ban_userid = 0 OR ban_exclude = 1)'; - } - else - { - $cache_ttl = ($user_id == ANONYMOUS) ? 3600 : 0; - $_sql = '(ban_userid = ' . $user_id; - - if ($user_email !== false) - { - $_sql .= " OR ban_email <> ''"; - } - - if ($user_ips !== false) - { - $_sql .= " OR ban_ip <> ''"; - } - - $_sql .= ')'; - - $where_sql[] = $_sql; - } - - $sql .= (sizeof($where_sql)) ? implode(' AND ', $where_sql) : ''; - $result = $db->sql_query($sql, $cache_ttl); - - $ban_triggered_by = 'user'; - while ($row = $db->sql_fetchrow($result)) - { - if ($row['ban_end'] && $row['ban_end'] < time()) - { - continue; - } - - $ip_banned = false; - if (!empty($row['ban_ip'])) - { - if (!is_array($user_ips)) - { - $ip_banned = preg_match('#^' . str_replace('\*', '.*?', preg_quote($row['ban_ip'], '#')) . '$#i', $user_ips); - } - else - { - foreach ($user_ips as $user_ip) - { - if (preg_match('#^' . str_replace('\*', '.*?', preg_quote($row['ban_ip'], '#')) . '$#i', $user_ip)) - { - $ip_banned = true; - break; - } - } - } - } - - if ((!empty($row['ban_userid']) && intval($row['ban_userid']) == $user_id) || - $ip_banned || - (!empty($row['ban_email']) && preg_match('#^' . str_replace('\*', '.*?', preg_quote($row['ban_email'], '#')) . '$#i', $user_email))) - { - if (!empty($row['ban_exclude'])) - { - $banned = false; - break; - } - else - { - $banned = true; - $ban_row = $row; - - if (!empty($row['ban_userid']) && intval($row['ban_userid']) == $user_id) - { - $ban_triggered_by = 'user'; - } - else if ($ip_banned) - { - $ban_triggered_by = 'ip'; - } - else - { - $ban_triggered_by = 'email'; - } - - // Don't break. Check if there is an exclude rule for this user - } - } - } - $db->sql_freeresult($result); - - if ($banned && !$return) - { - global $template; - - // If the session is empty we need to create a valid one... - if (empty($this->session_id)) - { - // This seems to be no longer needed? - #14971 -// $this->session_create(ANONYMOUS); - } - - // Initiate environment ... since it won't be set at this stage - $this->setup(); - - // Logout the user, banned users are unable to use the normal 'logout' link - if ($this->data['user_id'] != ANONYMOUS) - { - $this->session_kill(); - } - - // We show a login box here to allow founders accessing the board if banned by IP - if (defined('IN_LOGIN') && $this->data['user_id'] == ANONYMOUS) - { - global $phpEx; - - $this->setup('ucp'); - $this->data['is_registered'] = $this->data['is_bot'] = false; - - // Set as a precaution to allow login_box() handling this case correctly as well as this function not being executed again. - define('IN_CHECK_BAN', 1); - - login_box("index.$phpEx"); - - // The false here is needed, else the user is able to circumvent the ban. - $this->session_kill(false); - } - - // Ok, we catch the case of an empty session id for the anonymous user... - // This can happen if the user is logging in, banned by username and the login_box() being called "again". - if (empty($this->session_id) && defined('IN_CHECK_BAN')) - { - $this->session_create(ANONYMOUS); - } - - - // Determine which message to output - $till_date = ($ban_row['ban_end']) ? $this->format_date($ban_row['ban_end']) : ''; - $message = ($ban_row['ban_end']) ? 'BOARD_BAN_TIME' : 'BOARD_BAN_PERM'; - - $message = sprintf($this->lang[$message], $till_date, '', ''); - $message .= ($ban_row['ban_give_reason']) ? '

' . sprintf($this->lang['BOARD_BAN_REASON'], $ban_row['ban_give_reason']) : ''; - $message .= '

' . $this->lang['BAN_TRIGGERED_BY_' . strtoupper($ban_triggered_by)] . ''; - - // To circumvent session_begin returning a valid value and the check_ban() not called on second page view, we kill the session again - $this->session_kill(false); - - // A very special case... we are within the cron script which is not supposed to print out the ban message... show blank page - if (defined('IN_CRON')) - { - garbage_collection(); - exit_handler(); - exit; - } - - trigger_error($message); - } - - return ($banned && $ban_row['ban_give_reason']) ? $ban_row['ban_give_reason'] : $banned; - } - - /** - * Check if ip is blacklisted - * This should be called only where absolutly necessary - * - * Only IPv4 (rbldns does not support AAAA records/IPv6 lookups) - * - * @author satmd (from the php manual) - * @param string $mode register/post - spamcop for example is ommitted for posting - * @return false if ip is not blacklisted, else an array([checked server], [lookup]) - */ - function check_dnsbl($mode, $ip = false) - { - if ($ip === false) - { - $ip = $this->ip; - } - - // Neither Spamhaus nor Spamcop supports IPv6 addresses. - if (strpos($ip, ':') !== false) - { - return false; - } - - $dnsbl_check = array( - 'sbl.spamhaus.org' => 'http://www.spamhaus.org/query/bl?ip=', - ); - - if ($mode == 'register') - { - $dnsbl_check['bl.spamcop.net'] = 'http://spamcop.net/bl.shtml?'; - } - - if ($ip) - { - $quads = explode('.', $ip); - $reverse_ip = $quads[3] . '.' . $quads[2] . '.' . $quads[1] . '.' . $quads[0]; - - // Need to be listed on all servers... - $listed = true; - $info = array(); - - foreach ($dnsbl_check as $dnsbl => $lookup) - { - if (phpbb_checkdnsrr($reverse_ip . '.' . $dnsbl . '.', 'A') === true) - { - $info = array($dnsbl, $lookup . $ip); - } - else - { - $listed = false; - } - } - - if ($listed) - { - return $info; - } - } - - return false; - } - - /** - * Check if URI is blacklisted - * This should be called only where absolutly necessary, for example on the submitted website field - * This function is not in use at the moment and is only included for testing purposes, it may not work at all! - * This means it is untested at the moment and therefore commented out - * - * @param string $uri URI to check - * @return true if uri is on blacklist, else false. Only blacklist is checked (~zero FP), no grey lists - function check_uribl($uri) - { - // Normally parse_url() is not intended to parse uris - // We need to get the top-level domain name anyway... change. - $uri = parse_url($uri); - - if ($uri === false || empty($uri['host'])) - { - return false; - } - - $uri = trim($uri['host']); - - if ($uri) - { - // One problem here... the return parameter for the "windows" method is different from what - // we expect... this may render this check useless... - if (phpbb_checkdnsrr($uri . '.multi.uribl.com.', 'A') === true) - { - return true; - } - } - - return false; - } - */ - - /** - * Set/Update a persistent login key - * - * This method creates or updates a persistent session key. When a user makes - * use of persistent (formerly auto-) logins a key is generated and stored in the - * DB. When they revisit with the same key it's automatically updated in both the - * DB and cookie. Multiple keys may exist for each user representing different - * browsers or locations. As with _any_ non-secure-socket no passphrase login this - * remains vulnerable to exploit. - */ - function set_login_key($user_id = false, $key = false, $user_ip = false) - { - global $config, $db; - - $user_id = ($user_id === false) ? $this->data['user_id'] : $user_id; - $user_ip = ($user_ip === false) ? $this->ip : $user_ip; - $key = ($key === false) ? (($this->cookie_data['k']) ? $this->cookie_data['k'] : false) : $key; - - $key_id = unique_id(hexdec(substr($this->session_id, 0, 8))); - - $sql_ary = array( - 'key_id' => (string) md5($key_id), - 'last_ip' => (string) $this->ip, - 'last_login' => (int) time() - ); - - if (!$key) - { - $sql_ary += array( - 'user_id' => (int) $user_id - ); - } - - if ($key) - { - $sql = 'UPDATE ' . SESSIONS_KEYS_TABLE . ' - SET ' . $db->sql_build_array('UPDATE', $sql_ary) . ' - WHERE user_id = ' . (int) $user_id . " - AND key_id = '" . $db->sql_escape(md5($key)) . "'"; - } - else - { - $sql = 'INSERT INTO ' . SESSIONS_KEYS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary); - } - $db->sql_query($sql); - - $this->cookie_data['k'] = $key_id; - - return false; - } - - /** - * Reset all login keys for the specified user - * - * This method removes all current login keys for a specified (or the current) - * user. It will be called on password change to render old keys unusable - */ - function reset_login_keys($user_id = false) - { - global $config, $db; - - $user_id = ($user_id === false) ? (int) $this->data['user_id'] : (int) $user_id; - - $sql = 'DELETE FROM ' . SESSIONS_KEYS_TABLE . ' - WHERE user_id = ' . (int) $user_id; - $db->sql_query($sql); - - // If the user is logged in, update last visit info first before deleting sessions - $sql = 'SELECT session_time, session_page - FROM ' . SESSIONS_TABLE . ' - WHERE session_user_id = ' . (int) $user_id . ' - ORDER BY session_time DESC'; - $result = $db->sql_query_limit($sql, 1); - $row = $db->sql_fetchrow($result); - $db->sql_freeresult($result); - - if ($row) - { - $sql = 'UPDATE ' . USERS_TABLE . ' - SET user_lastvisit = ' . (int) $row['session_time'] . ", user_lastpage = '" . $db->sql_escape($row['session_page']) . "' - WHERE user_id = " . (int) $user_id; - $db->sql_query($sql); - } - - // Let's also clear any current sessions for the specified user_id - // If it's the current user then we'll leave this session intact - $sql_where = 'session_user_id = ' . (int) $user_id; - $sql_where .= ($user_id === (int) $this->data['user_id']) ? " AND session_id <> '" . $db->sql_escape($this->session_id) . "'" : ''; - - $sql = 'DELETE FROM ' . SESSIONS_TABLE . " - WHERE $sql_where"; - $db->sql_query($sql); - - // We're changing the password of the current user and they have a key - // Lets regenerate it to be safe - if ($user_id === (int) $this->data['user_id'] && $this->cookie_data['k']) - { - $this->set_login_key($user_id); - } - } - - - /** - * Check if the request originated from the same page. - * @param bool $check_script_path If true, the path will be checked as well - */ - function validate_referer($check_script_path = false) - { - global $config, $request; - - // no referer - nothing to validate, user's fault for turning it off (we only check on POST; so meta can't be the reason) - if (empty($this->referer) || empty($this->host)) - { - return true; - } - - $host = htmlspecialchars($this->host); - $ref = substr($this->referer, strpos($this->referer, '://') + 3); - - if (!(stripos($ref, $host) === 0) && (!$config['force_server_vars'] || !(stripos($ref, $config['server_name']) === 0))) - { - return false; - } - else if ($check_script_path && rtrim($this->page['root_script_path'], '/') !== '') - { - $ref = substr($ref, strlen($host)); - $server_port = $request->server('SERVER_PORT', 0); - - if ($server_port !== 80 && $server_port !== 443 && stripos($ref, ":$server_port") === 0) - { - $ref = substr($ref, strlen(":$server_port")); - } - - if (!(stripos(rtrim($ref, '/'), rtrim($this->page['root_script_path'], '/')) === 0)) - { - return false; - } - } - - return true; - } - - - function unset_admin() - { - global $db; - $sql = 'UPDATE ' . SESSIONS_TABLE . ' - SET session_admin = 0 - WHERE session_id = \'' . $db->sql_escape($this->session_id) . '\''; - $db->sql_query($sql); - } - - /** - * Update the session data - * - * @param array $session_data associative array of session keys to be updated - * @param string $session_id optional session_id, defaults to current user's session_id - */ - public function update_session($session_data, $session_id = null) - { - global $db; - - $session_id = ($session_id) ? $session_id : $this->session_id; - - $sql = 'UPDATE ' . SESSIONS_TABLE . ' SET ' . $db->sql_build_array('UPDATE', $session_data) . " - WHERE session_id = '" . $db->sql_escape($session_id) . "'"; - $db->sql_query($sql); - } -} diff --git a/phpBB/includes/style/extension_path_provider.php b/phpBB/includes/style/extension_path_provider.php deleted file mode 100644 index ec1d85f821..0000000000 --- a/phpBB/includes/style/extension_path_provider.php +++ /dev/null @@ -1,137 +0,0 @@ -base_path_provider = $base_path_provider; - $this->phpbb_root_path = $phpbb_root_path; - } - - /** - * Sets a prefix for style paths searched within extensions. - * - * The prefix is inserted between the extension's path e.g. ext/foo/ and - * the looked up style path, e.g. styles/bar/. So it should not have a - * leading slash, but should have a trailing slash. - * - * @param string $ext_dir_prefix The prefix including trailing slash - * @return null - */ - public function set_ext_dir_prefix($ext_dir_prefix) - { - $this->ext_dir_prefix = $ext_dir_prefix; - } - - /** - * Finds style paths using the extension manager - * - * Locates a path (e.g. styles/prosilver/) in all active extensions. - * Then appends the core style paths based in the current working - * directory. - * - * @return array List of style paths - */ - public function find() - { - $directories = array(); - - $finder = $this->extension_manager->get_finder(); - foreach ($this->base_path_provider as $key => $paths) - { - if ($key == 'style') - { - foreach ($paths as $path) - { - $directories['style'][] = $path; - if ($path && !phpbb_is_absolute($path)) - { - // Remove phpBB root path from the style path, - // so the finder is able to find extension styles, - // when the root path is not ./ - if (strpos($path, $this->phpbb_root_path) === 0) - { - $path = substr($path, strlen($this->phpbb_root_path)); - } - - $result = $finder->directory('/' . $this->ext_dir_prefix . $path) - ->get_directories(true, false, true); - foreach ($result as $ext => $ext_path) - { - // Make sure $ext_path has no ending slash - if (substr($ext_path, -1) === '/') - { - $ext_path = substr($ext_path, 0, -1); - } - $directories[$ext][] = $ext_path; - } - } - } - } - } - - return $directories; - } - - /** - * Overwrites the current style paths - * - * @param array $styles An array of style paths. The first element is the main style. - * @return null - */ - public function set_styles(array $styles) - { - $this->base_path_provider->set_styles($styles); - $this->items = null; - } -} diff --git a/phpBB/includes/style/path_provider.php b/phpBB/includes/style/path_provider.php deleted file mode 100644 index 731d682e88..0000000000 --- a/phpBB/includes/style/path_provider.php +++ /dev/null @@ -1,62 +0,0 @@ -paths = array('style' => $styles); - } - - /** - * Retrieve an iterator over all style paths - * - * @return ArrayIterator An iterator for the array of style paths - */ - public function getIterator() - { - return new ArrayIterator($this->paths); - } -} diff --git a/phpBB/includes/style/path_provider_interface.php b/phpBB/includes/style/path_provider_interface.php deleted file mode 100644 index 1a6153a4d3..0000000000 --- a/phpBB/includes/style/path_provider_interface.php +++ /dev/null @@ -1,42 +0,0 @@ -set_default_template_path(); - } - - /** - * Sets the list of style paths - * - * These paths will be searched for style files in the provided order. - * Paths may be outside of phpBB, but templates loaded from these paths - * will still be cached. - * - * @param array $style_paths An array of paths to style directories - * @return null - */ - public function set_paths($style_paths) - { - $this->roots = array(); - $this->files = array(); - $this->filenames = array(); - - foreach ($style_paths as $key => $paths) - { - foreach ($paths as $path) - { - // Make sure $path has no ending slash - if (substr($path, -1) === '/') - { - $path = substr($path, 0, -1); - } - $this->roots[$key][] = $path; - } - } - } - - /** - * Sets the location of templates directory within style directories. - * - * The location must be a relative path, with a trailing slash. - * Typically it is one directory level deep, e.g. "template/". - * - * @param string $template_path Relative path to templates directory within style directories - * @return null - */ - public function set_template_path($template_path) - { - $this->template_path = $template_path; - } - - /** - * Sets the location of templates directory within style directories - * to the default, which is "template/". - * - * @return null - */ - public function set_default_template_path() - { - $this->template_path = 'template/'; - } - - /** - * {@inheritDoc} - */ - public function set_filenames(array $filename_array) - { - foreach ($filename_array as $handle => $filename) - { - if (empty($filename)) - { - trigger_error("style resource locator: set_filenames: Empty filename specified for $handle", E_USER_ERROR); - } - - $this->filename[$handle] = $filename; - - foreach ($this->roots as $root_key => $root_paths) - { - foreach ($root_paths as $root_index => $root) - { - $this->files[$root_key][$root_index][$handle] = $root . '/' . $this->template_path . $filename; - } - } - } - } - - /** - * {@inheritDoc} - */ - public function get_filename_for_handle($handle) - { - if (!isset($this->filename[$handle])) - { - trigger_error("style resource locator: get_filename_for_handle: No file specified for handle $handle", E_USER_ERROR); - } - return $this->filename[$handle]; - } - - /** - * {@inheritDoc} - */ - public function get_virtual_source_file_for_handle($handle) - { - // If we don't have a file assigned to this handle, die. - if (!isset($this->files['style'][0][$handle])) - { - trigger_error("style resource locator: No file specified for handle $handle", E_USER_ERROR); - } - - $source_file = $this->files['style'][0][$handle]; - return $source_file; - } - - /** - * {@inheritDoc} - */ - public function get_source_file_for_handle($handle, $find_all = false) - { - // If we don't have a file assigned to this handle, die. - if (!isset($this->files['style'][0][$handle])) - { - trigger_error("style resource locator: No file specified for handle $handle", E_USER_ERROR); - } - - // locate a source file that exists - $source_file = $this->files['style'][0][$handle]; - $tried = $source_file; - $found = false; - $found_all = array(); - foreach ($this->roots as $root_key => $root_paths) - { - foreach ($root_paths as $root_index => $root) - { - $source_file = $this->files[$root_key][$root_index][$handle]; - $tried .= ', ' . $source_file; - if (file_exists($source_file)) - { - $found = true; - break; - } - } - if ($found) - { - if ($find_all) - { - $found_all[] = $source_file; - $found = false; - } - else - { - break; - } - } - } - - // search failed - if (!$found && !$find_all) - { - trigger_error("style resource locator: File for handle $handle does not exist. Could not find: $tried", E_USER_ERROR); - } - - return ($find_all) ? $found_all : $source_file; - } - - /** - * {@inheritDoc} - */ - public function get_first_file_location($files, $return_default = false, $return_full_path = true) - { - // set default value - $default_result = false; - - // check all available paths - foreach ($this->roots as $root_paths) - { - foreach ($root_paths as $path) - { - // check all files - foreach ($files as $filename) - { - $source_file = $path . '/' . $filename; - if (file_exists($source_file)) - { - return ($return_full_path) ? $source_file : $filename; - } - - // assign first file as result if $return_default is true - if ($return_default && $default_result === false) - { - $default_result = $source_file; - } - } - } - } - - // search failed - return $default_result; - } - - /** - * Obtains filesystem path for a template file. - * - * The simplest use is specifying a single template file as a string - * in the first argument. This template file should be a basename - * of a template file in the selected style, or its parent styles - * if template inheritance is being utilized. - * - * Note: "selected style" is whatever style the style resource locator - * is configured for. - * - * The return value then will be a path, relative to the current - * directory or absolute, to the template file in the selected style - * or its closest parent. - * - * If the selected style does not have the template file being searched, - * (and if inheritance is involved, none of the parents have it either), - * false will be returned. - * - * Specifying true for $return_default will cause the function to - * return the first path which was checked for existence in the event - * that the template file was not found, instead of false. - * This is the path in the selected style itself, not any of its - * parents. - * - * $files can be given an array of templates instead of a single - * template. When given an array, the function will try to resolve - * each template in the array to a path, and will return the first - * path that exists, or false if none exist. - * - * If $files is an array and template inheritance is involved, first - * each of the files will be checked in the selected style, then each - * of the files will be checked in the immediate parent, and so on. - * - * If $return_full_path is false, then instead of returning a usable - * path (when the template is found) only the template's basename - * will be returned. This can be used to check which of the templates - * specified in $files exists. Naturally more than one template must - * be given in $files. - * - * This function works identically to get_first_file_location except - * it operates on a list of templates, not files. Practically speaking, - * the templates given in the first argument first are prepended with - * the template path (property in this class), then given to - * get_first_file_location for the rest of the processing. - * - * Templates given to this function can be relative paths for templates - * located in subdirectories of the template directories. The paths - * should be relative to the templates directory (template/ by default). - * - * @param string or array $files List of templates to locate. If there is only - * one template, $files can be a string to make code easier to read. - * @param bool $return_default Determines what to return if template does not - * exist. If true, function will return location where template is - * supposed to be. If false, function will return false. - * @param bool $return_full_path If true, function will return full path - * to template. If false, function will return template file name. - * This parameter can be used to check which one of set of template - * files is available. - * @return string or boolean Source template path if template exists or $return_default is - * true. False if template does not exist and $return_default is false - */ - public function get_first_template_location($templates, $return_default = false, $return_full_path = true) - { - // add template path prefix - $files = array(); - if (is_string($templates)) - { - $files[] = $this->template_path . $templates; - } - else - { - foreach ($templates as $template) - { - $files[] = $this->template_path . $template; - } - } - - return $this->get_first_file_location($files, $return_default, $return_full_path); - } -} diff --git a/phpBB/includes/style/style.php b/phpBB/includes/style/style.php deleted file mode 100644 index 034f518091..0000000000 --- a/phpBB/includes/style/style.php +++ /dev/null @@ -1,241 +0,0 @@ -phpbb_root_path = $phpbb_root_path; - $this->php_ext = $php_ext; - $this->config = $config; - $this->user = $user; - $this->locator = $locator; - $this->provider = $provider; - $this->template = $template; - } - - /** - * Get the style tree of the style preferred by the current user - * - * @return array Style tree, most specific first - */ - public function get_user_style() - { - $style_list = array( - $this->user->style['style_path'], - ); - - if ($this->user->style['style_parent_id']) - { - $style_list = array_merge($style_list, array_reverse(explode('/', $this->user->style['style_parent_tree']))); - } - - return $style_list; - } - - /** - * Set style location based on (current) user's chosen style. - * - * @param array $style_directories The directories to add style paths for - * E.g. array('ext/foo/bar/styles', 'styles') - * Default: array('styles') (phpBB's style directory) - * @return bool true - */ - public function set_style($style_directories = array('styles')) - { - $this->names = $this->get_user_style(); - - $paths = array(); - foreach ($style_directories as $directory) - { - foreach ($this->names as $name) - { - $path = $this->get_style_path($name, $directory); - - if (is_dir($path)) - { - $paths[] = $path; - } - } - } - - $this->provider->set_styles($paths); - $this->locator->set_paths($this->provider); - - $new_paths = array(); - foreach ($paths as $path) - { - $new_paths[] = $path . '/template/'; - } - - $this->template->set_style_names($this->names, $new_paths, ($style_directories === array('styles'))); - - return true; - } - - /** - * Set custom style location (able to use directory outside of phpBB). - * - * Note: Templates are still compiled to phpBB's cache directory. - * - * @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/). - * @return bool true - */ - 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); - - if ($template_path !== false) - { - $this->locator->set_template_path($template_path); - } - - $new_paths = array(); - foreach ($paths as $path) - { - $new_paths[] = $path . '/' . (($template_path !== false) ? $template_path : 'template/'); - } - - $this->template->set_style_names($names, $new_paths); - - return true; - } - - /** - * Get location of style directory for specific style_path - * - * @param string $path Style path, such as "prosilver" - * @param string $style_base_directory The base directory the style is in - * E.g. 'styles', 'ext/foo/bar/styles' - * Default: 'styles' - * @return string Path to style directory, relative to current path - */ - public function get_style_path($path, $style_base_directory = 'styles') - { - return $this->phpbb_root_path . trim($style_base_directory, '/') . '/' . $path; - } - - /** - * Defines a prefix to use for style paths in extensions - * - * @param string $ext_dir_prefix The prefix including trailing slash - * @return null - */ - public function set_ext_dir_prefix($ext_dir_prefix) - { - $this->provider->set_ext_dir_prefix($ext_dir_prefix); - } - - /** - * Locates source file path, accounting for styles tree and verifying that - * the path exists. - * - * @param string or array $files List of files to locate. If there is only - * one file, $files can be a string to make code easier to read. - * @param bool $return_default Determines what to return if file does not - * exist. If true, function will return location where file is - * supposed to be. If false, function will return false. - * @param bool $return_full_path If true, function will return full path - * to file. If false, function will return file name. This - * parameter can be used to check which one of set of files - * is available. - * @return string or boolean Source file path if file exists or $return_default is - * true. False if file does not exist and $return_default is false - */ - public function locate($files, $return_default = false, $return_full_path = true) - { - // convert string to array - if (is_string($files)) - { - $files = array($files); - } - - // use resource locator to find files - return $this->locator->get_first_file_location($files, $return_default, $return_full_path); - } -} diff --git a/phpBB/includes/template/asset.php b/phpBB/includes/template/asset.php deleted file mode 100644 index 7c322cd971..0000000000 --- a/phpBB/includes/template/asset.php +++ /dev/null @@ -1,182 +0,0 @@ -set_url($url); - } - - /** - * Set URL - * - * @param string $url URL - */ - public function set_url($url) - { - if (version_compare(PHP_VERSION, '5.4.7') < 0 && substr($url, 0, 2) === '//') - { - // Workaround for PHP 5.4.6 and older bug #62844 - add fake scheme and then remove it - $this->components = parse_url('http:' . $url); - $this->components['scheme'] = ''; - return; - } - $this->components = parse_url($url); - } - - /** - * Convert URL components into string - * - * @param array $components URL components - * @return string URL - */ - protected function join_url($components) - { - $path = ''; - if (isset($components['scheme'])) - { - $path = $components['scheme'] === '' ? '//' : $components['scheme'] . '://'; - } - - if (isset($components['user']) || isset($components['pass'])) - { - if ($path === '' && !isset($components['port'])) - { - $path = '//'; - } - $path .= $components['user']; - if (isset($components['pass'])) - { - $path .= ':' . $components['pass']; - } - $path .= '@'; - } - - if (isset($components['host'])) - { - if ($path === '' && !isset($components['port'])) - { - $path = '//'; - } - $path .= $components['host']; - if (isset($components['port'])) - { - $path .= ':' . $components['port']; - } - } - - if (isset($components['path'])) - { - $path .= $components['path']; - } - - if (isset($components['query'])) - { - $path .= '?' . $components['query']; - } - - if (isset($components['fragment'])) - { - $path .= '#' . $components['fragment']; - } - - return $path; - } - - /** - * Get URL - * - * @return string URL - */ - public function get_url() - { - return $this->join_url($this->components); - } - - /** - * Checks if URL is local and relative - * - * @return boolean True if URL is local and relative - */ - public function is_relative() - { - if (empty($this->components) || !isset($this->components['path'])) - { - // Invalid URL - return false; - } - return !isset($this->components['scheme']) && !isset($this->components['host']) && substr($this->components['path'], 0, 1) !== '/'; - } - - /** - * Get path component of current URL - * - * @return string Path - */ - public function get_path() - { - return isset($this->components['path']) ? $this->components['path'] : ''; - } - - /** - * Set path component - * - * @param string $path Path component - * @param boolean $urlencode If true, parts of path should be encoded with rawurlencode() - */ - public function set_path($path, $urlencode = false) - { - if ($urlencode) - { - $paths = explode('/', $path); - foreach ($paths as &$dir) - { - $dir = rawurlencode($dir); - } - $path = implode('/', $paths); - } - $this->components['path'] = $path; - } - - /** - * Add assets_version parameter to URL. - * Parameter will not be added if assets_version already exists in URL - * - * @param string $version Version - */ - public function add_assets_version($version) - { - if (!isset($this->components['query'])) - { - $this->components['query'] = 'assets_version=' . $version; - return; - } - $query = $this->components['query']; - if (!preg_match('/(^|[&;])assets_version=/', $query)) - { - $this->components['query'] = $query . '&assets_version=' . $version; - } - } -} diff --git a/phpBB/includes/template/context.php b/phpBB/includes/template/context.php deleted file mode 100644 index c5ce7422b9..0000000000 --- a/phpBB/includes/template/context.php +++ /dev/null @@ -1,389 +0,0 @@ - $this->tpldata[block][iteration#][child][iteration#][child2][iteration#][variablename] == value - * if it's a root-level variable, it'll be like this: - * --> $this->tpldata[.][0][varname] == value - * - * @var array - */ - private $tpldata = array('.' => array(0 => array())); - - /** - * @var array Reference to template->tpldata['.'][0] - */ - private $rootref; - - public function __construct() - { - $this->clear(); - } - - /** - * Clears template data set. - */ - public function clear() - { - $this->tpldata = array('.' => array(0 => array())); - $this->rootref = &$this->tpldata['.'][0]; - } - - /** - * Assign a single scalar value to a single key. - * - * Value can be a string, an integer or a boolean. - * - * @param string $varname Variable name - * @param string $varval Value to assign to variable - */ - public function assign_var($varname, $varval) - { - $this->rootref[$varname] = $varval; - - return true; - } - - /** - * Append text to the string value stored in a key. - * - * Text is appended using the string concatenation operator (.). - * - * @param string $varname Variable name - * @param string $varval Value to append to variable - */ - public function append_var($varname, $varval) - { - $this->rootref[$varname] = (isset($this->rootref[$varname]) ? $this->rootref[$varname] : '') . $varval; - - return true; - } - - /** - * Returns a reference to template data array. - * - * This function is public so that template renderer may invoke it. - * Users should alter template variables via functions in phpbb_template. - * - * Note: modifying returned array will affect data stored in the context. - * - * @return array template data - */ - public function &get_data_ref() - { - // returning a reference directly is not - // something php is capable of doing - $ref = &$this->tpldata; - return $ref; - } - - /** - * Returns a reference to template root scope. - * - * This function is public so that template renderer may invoke it. - * Users should not need to invoke this function. - * - * Note: modifying returned array will affect data stored in the context. - * - * @return array template data - */ - public function &get_root_ref() - { - // rootref is already a reference - return $this->rootref; - } - - /** - * Assign key variable pairs from an array to a specified block - * - * @param string $blockname Name of block to assign $vararray to - * @param array $vararray A hash of variable name => value pairs - */ - public function assign_block_vars($blockname, array $vararray) - { - if (strpos($blockname, '.') !== false) - { - // Nested block. - $blocks = explode('.', $blockname); - $blockcount = sizeof($blocks) - 1; - - $str = &$this->tpldata; - for ($i = 0; $i < $blockcount; $i++) - { - $str = &$str[$blocks[$i]]; - $str = &$str[sizeof($str) - 1]; - } - - $s_row_count = isset($str[$blocks[$blockcount]]) ? sizeof($str[$blocks[$blockcount]]) : 0; - $vararray['S_ROW_COUNT'] = $vararray['S_ROW_NUM'] = $s_row_count; - - // Assign S_FIRST_ROW - if (!$s_row_count) - { - $vararray['S_FIRST_ROW'] = true; - } - - // Assign S_BLOCK_NAME - $vararray['S_BLOCK_NAME'] = $blocks[$blockcount]; - - // Now the tricky part, we always assign S_LAST_ROW and remove the entry before - // This is much more clever than going through the complete template data on display (phew) - $vararray['S_LAST_ROW'] = true; - if ($s_row_count > 0) - { - unset($str[$blocks[$blockcount]][($s_row_count - 1)]['S_LAST_ROW']); - } - - // Now we add the block that we're actually assigning to. - // We're adding a new iteration to this block with the given - // variable assignments. - $str[$blocks[$blockcount]][] = $vararray; - - // Set S_NUM_ROWS - foreach ($str[$blocks[$blockcount]] as &$mod_block) - { - $mod_block['S_NUM_ROWS'] = sizeof($str[$blocks[$blockcount]]); - } - } - else - { - // Top-level block. - $s_row_count = (isset($this->tpldata[$blockname])) ? sizeof($this->tpldata[$blockname]) : 0; - $vararray['S_ROW_COUNT'] = $vararray['S_ROW_NUM'] = $s_row_count; - - // Assign S_FIRST_ROW - if (!$s_row_count) - { - $vararray['S_FIRST_ROW'] = true; - } - - // Assign S_BLOCK_NAME - $vararray['S_BLOCK_NAME'] = $blockname; - - // We always assign S_LAST_ROW and remove the entry before - $vararray['S_LAST_ROW'] = true; - if ($s_row_count > 0) - { - unset($this->tpldata[$blockname][($s_row_count - 1)]['S_LAST_ROW']); - } - - // Add a new iteration to this block with the variable assignments we were given. - $this->tpldata[$blockname][] = $vararray; - - // Set S_NUM_ROWS - foreach ($this->tpldata[$blockname] as &$mod_block) - { - $mod_block['S_NUM_ROWS'] = sizeof($this->tpldata[$blockname]); - } - } - - return true; - } - - /** - * Change already assigned key variable pair (one-dimensional - single loop entry) - * - * An example of how to use this function: - * {@example alter_block_array.php} - * - * @param string $blockname the blockname, for example 'loop' - * @param array $vararray the var array to insert/add or merge - * @param mixed $key Key to search for - * - * array: KEY => VALUE [the key/value pair to search for within the loop to determine the correct position] - * - * int: Position [the position to change or insert at directly given] - * - * If key is false the position is set to 0 - * If key is true the position is set to the last entry - * - * @param string $mode Mode to execute (valid modes are 'insert' and 'change') - * - * If insert, the vararray is inserted at the given position (position counting from zero). - * If change, the current block gets merged with the vararray (resulting in new key/value pairs be added and existing keys be replaced by the new value). - * - * Since counting begins by zero, inserting at the last position will result in this array: array(vararray, last positioned array) - * and inserting at position 1 will result in this array: array(first positioned array, vararray, following vars) - * - * @return bool false on error, true on success - */ - public function alter_block_array($blockname, array $vararray, $key = false, $mode = 'insert') - { - if (strpos($blockname, '.') !== false) - { - // Nested block. - $blocks = explode('.', $blockname); - $blockcount = sizeof($blocks) - 1; - - $block = &$this->tpldata; - for ($i = 0; $i < $blockcount; $i++) - { - if (($pos = strpos($blocks[$i], '[')) !== false) - { - $name = substr($blocks[$i], 0, $pos); - - if (strpos($blocks[$i], '[]') === $pos) - { - $index = sizeof($block[$name]) - 1; - } - else - { - $index = min((int) substr($blocks[$i], $pos + 1, -1), sizeof($block[$name]) - 1); - } - } - else - { - $name = $blocks[$i]; - $index = sizeof($block[$name]) - 1; - } - $block = &$block[$name]; - $block = &$block[$index]; - } - - $block = &$block[$blocks[$i]]; // Traverse the last block - } - else - { - // Top-level block. - $block = &$this->tpldata[$blockname]; - } - - // Change key to zero (change first position) if false and to last position if true - if ($key === false || $key === true) - { - $key = ($key === false) ? 0 : sizeof($block); - } - - // Get correct position if array given - if (is_array($key)) - { - // Search array to get correct position - list($search_key, $search_value) = @each($key); - - $key = NULL; - foreach ($block as $i => $val_ary) - { - if ($val_ary[$search_key] === $search_value) - { - $key = $i; - break; - } - } - - // key/value pair not found - if ($key === NULL) - { - return false; - } - } - - // Insert Block - if ($mode == 'insert') - { - // Make sure we are not exceeding the last iteration - if ($key >= sizeof($this->tpldata[$blockname])) - { - $key = sizeof($this->tpldata[$blockname]); - unset($this->tpldata[$blockname][($key - 1)]['S_LAST_ROW']); - $vararray['S_LAST_ROW'] = true; - } - else if ($key === 0) - { - unset($this->tpldata[$blockname][0]['S_FIRST_ROW']); - $vararray['S_FIRST_ROW'] = true; - } - - // Assign S_BLOCK_NAME - $vararray['S_BLOCK_NAME'] = $blockname; - - // Re-position template blocks - for ($i = sizeof($block); $i > $key; $i--) - { - $block[$i] = $block[$i-1]; - - $block[$i]['S_ROW_COUNT'] = $block[$i]['S_ROW_NUM'] = $i; - } - - // Insert vararray at given position - $block[$key] = $vararray; - $block[$key]['S_ROW_COUNT'] = $block[$key]['S_ROW_NUM'] = $key; - - // Set S_NUM_ROWS - foreach ($this->tpldata[$blockname] as &$mod_block) - { - $mod_block['S_NUM_ROWS'] = sizeof($this->tpldata[$blockname]); - } - - return true; - } - - // Which block to change? - if ($mode == 'change') - { - if ($key == sizeof($block)) - { - $key--; - } - - $block[$key] = array_merge($block[$key], $vararray); - - return true; - } - - return false; - } - - /** - * Reset/empty complete block - * - * @param string $blockname Name of block to destroy - */ - public function destroy_block_vars($blockname) - { - if (strpos($blockname, '.') !== false) - { - // Nested block. - $blocks = explode('.', $blockname); - $blockcount = sizeof($blocks) - 1; - - $str = &$this->tpldata; - for ($i = 0; $i < $blockcount; $i++) - { - $str = &$str[$blocks[$i]]; - $str = &$str[sizeof($str) - 1]; - } - - unset($str[$blocks[$blockcount]]); - } - else - { - // Top-level block. - unset($this->tpldata[$blockname]); - } - - return true; - } -} diff --git a/phpBB/includes/template/locator.php b/phpBB/includes/template/locator.php deleted file mode 100644 index f6fd20bcc2..0000000000 --- a/phpBB/includes/template/locator.php +++ /dev/null @@ -1,163 +0,0 @@ - filename pairs. - * - * @param array $filename_array Should be a hash of handle => filename pairs. - */ - public function set_filenames(array $filename_array); - - /** - * Determines the filename for a template handle. - * - * The filename comes from array used in a set_filenames call, - * which should have been performed prior to invoking this function. - * Return value is a file basename (without path). - * - * @param $handle string Template handle - * @return string Filename corresponding to the template handle - */ - public function get_filename_for_handle($handle); - - /** - * Determines the source file path for a template handle without - * regard for styles tree. - * - * This function returns the path in "primary" style directory - * corresponding to the given template handle. That path may or - * may not actually exist on the filesystem. Because this function - * does not perform stat calls to determine whether the path it - * 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 - * directory if primary style has parent styles). - * - * This function will trigger an error if the handle was never - * associated with a template file via set_filenames. - * - * @param $handle string Template handle - * @return string Path to source file path in primary style directory - */ - public function get_virtual_source_file_for_handle($handle); - - /** - * Determines the source file path for a template handle, accounting - * for styles tree and verifying that the path exists. - * - * This function returns the actual path that may be compiled for - * the specified template handle. It will trigger an error if - * the template handle was never associated with a template path - * via set_filenames or if the template file does not exist on the - * filesystem. - * - * Use get_virtual_source_file_for_handle to just resolve a template - * handle to a path without any filesystem or styles tree checks. - * - * @param string $handle Template handle (i.e. "friendly" template name) - * @param bool $find_all If true, each root path will be checked and function - * will return array of files instead of string and will not - * trigger a error if template does not exist - * @return string Source file path - */ - public function get_source_file_for_handle($handle, $find_all = false); - - /** - * Obtains a complete filesystem path for a file in a style. - * - * This function traverses the style tree (selected style and - * its parents in order, if inheritance is being used) and finds - * the first file on the filesystem matching specified relative path, - * or the first of the specified paths if more than one path is given. - * - * This function can be used to determine filesystem path of any - * file under any style, with the consequence being that complete - * relative to the style directory path must be provided as an argument. - * - * In particular, this function can be used to locate templates - * and javascript files. - * - * For locating templates get_first_template_location should be used - * as it prepends the configured template path to the template basename. - * - * Note: "selected style" is whatever style the style resource locator - * is configured for. - * - * The return value then will be a path, relative to the current - * directory or absolute, to the first existing file in the selected - * style or its closest parent. - * - * If the selected style does not have the file being searched, - * (and if inheritance is involved, none of the parents have it either), - * false will be returned. - * - * Multiple files can be specified, in which case the first file in - * the list that can be found on the filesystem is returned. - * - * If multiple files are specified and inheritance is involved, - * first each of the specified files is checked in the selected style, - * then each of the specified files is checked in the immediate parent, - * etc. - * - * Specifying true for $return_default will cause the function to - * return the first path which was checked for existence in the event - * that the template file was not found, instead of false. - * This is always a path in the selected style itself, not any of its - * parents. - * - * If $return_full_path is false, then instead of returning a usable - * path (when the file is found) the file's path relative to the style - * directory will be returned. This is the same path as was given to - * the function as a parameter. This can be used to check which of the - * files specified in $files exists. Naturally this requires passing - * more than one file in $files. - * - * @param array $files List of files to locate. - * @param bool $return_default Determines what to return if file does not - * exist. If true, function will return location where file is - * supposed to be. If false, function will return false. - * @param bool $return_full_path If true, function will return full path - * to file. If false, function will return file name. This - * parameter can be used to check which one of set of files - * is available. - * @return string or boolean Source file path if file exists or $return_default is - * true. False if file does not exist and $return_default is false - */ - public function get_first_file_location($files, $return_default = false, $return_full_path = true); -} diff --git a/phpBB/includes/template/template.php b/phpBB/includes/template/template.php deleted file mode 100644 index 89a01e924d..0000000000 --- a/phpBB/includes/template/template.php +++ /dev/null @@ -1,157 +0,0 @@ - filename pairs. - * @return phpbb_template $this - */ - public function set_filenames(array $filename_array); - - /** - * Sets the style names/paths corresponding to style hierarchy being compiled - * and/or rendered. - * - * @param array $style_names List of style names in inheritance tree order - * @param array $style_paths List of style paths in inheritance tree order - * @return phpbb_template $this - */ - public function set_style_names(array $style_names, array $style_paths); - - /** - * Clears all variables and blocks assigned to this template. - * - * @return phpbb_template $this - */ - public function destroy(); - - /** - * Reset/empty complete block - * - * @param string $blockname Name of block to destroy - * @return phpbb_template $this - */ - public function destroy_block_vars($blockname); - - /** - * Display a template for provided handle. - * - * The template will be loaded and compiled, if necessary, first. - * - * This function calls hooks. - * - * @param string $handle Handle to display - * @return phpbb_template $this - */ - public function display($handle); - - /** - * Display the handle and assign the output to a template variable - * or return the compiled result. - * - * @param string $handle Handle to operate on - * @param string $template_var Template variable to assign compiled handle to - * @param bool $return_content If true return compiled handle, otherwise assign to $template_var - * @return phpbb_template|string if $return_content is true return string of the compiled handle, otherwise return $this - */ - public function assign_display($handle, $template_var = '', $return_content = true); - - /** - * Assign key variable pairs from an array - * - * @param array $vararray A hash of variable name => value pairs - * @return phpbb_template $this - */ - public function assign_vars(array $vararray); - - /** - * Assign a single scalar value to a single key. - * - * Value can be a string, an integer or a boolean. - * - * @param string $varname Variable name - * @param string $varval Value to assign to variable - * @return phpbb_template $this - */ - public function assign_var($varname, $varval); - - /** - * Append text to the string value stored in a key. - * - * Text is appended using the string concatenation operator (.). - * - * @param string $varname Variable name - * @param string $varval Value to append to variable - * @return phpbb_template $this - */ - public function append_var($varname, $varval); - - /** - * Assign key variable pairs from an array to a specified block - * @param string $blockname Name of block to assign $vararray to - * @param array $vararray A hash of variable name => value pairs - * @return phpbb_template $this - */ - public function assign_block_vars($blockname, array $vararray); - - /** - * Change already assigned key variable pair (one-dimensional - single loop entry) - * - * An example of how to use this function: - * {@example alter_block_array.php} - * - * @param string $blockname the blockname, for example 'loop' - * @param array $vararray the var array to insert/add or merge - * @param mixed $key Key to search for - * - * array: KEY => VALUE [the key/value pair to search for within the loop to determine the correct position] - * - * int: Position [the position to change or insert at directly given] - * - * If key is false the position is set to 0 - * If key is true the position is set to the last entry - * - * @param string $mode Mode to execute (valid modes are 'insert' and 'change') - * - * If insert, the vararray is inserted at the given position (position counting from zero). - * If change, the current block gets merged with the vararray (resulting in new key/value pairs be added and existing keys be replaced by the new value). - * - * Since counting begins by zero, inserting at the last position will result in this array: array(vararray, last positioned array) - * and inserting at position 1 will result in this array: array(first positioned array, vararray, following vars) - * - * @return bool false on error, true on success - */ - public function alter_block_array($blockname, array $vararray, $key = false, $mode = 'insert'); - - /** - * Get path to template for handle (required for BBCode parser) - * - * @return string - */ - public function get_source_file_for_handle($handle); -} diff --git a/phpBB/includes/template/twig/definition.php b/phpBB/includes/template/twig/definition.php deleted file mode 100644 index 6557b209eb..0000000000 --- a/phpBB/includes/template/twig/definition.php +++ /dev/null @@ -1,69 +0,0 @@ -definitions[$name])) ? $this->definitions[$name] : null; - } - - /** - * DEFINE a variable - * - * @param string $name - * @param mixed $value - * @return phpbb_template_twig_definition - */ - public function set($name, $value) - { - $this->definitions[$name] = $value; - - return $this; - } - - /** - * Append to a variable - * - * @param string $name - * @param string $value - * @return phpbb_template_twig_definition - */ - public function append($name, $value) - { - if (!isset($this->definitions[$name])) - { - $this->definitions[$name] = ''; - } - - $this->definitions[$name] .= $value; - - return $this; - } -} diff --git a/phpBB/includes/template/twig/environment.php b/phpBB/includes/template/twig/environment.php deleted file mode 100644 index b60cd72325..0000000000 --- a/phpBB/includes/template/twig/environment.php +++ /dev/null @@ -1,140 +0,0 @@ - path) - * @param string $phpbb_root_path - * @param Twig_LoaderInterface $loader - * @param array $options Array of options to pass to Twig - */ - public function __construct($phpbb_config, $phpbb_extensions, $phpbb_root_path, Twig_LoaderInterface $loader = null, $options = array()) - { - $this->phpbb_config = $phpbb_config; - $this->phpbb_extensions = $phpbb_extensions; - $this->phpbb_root_path = $phpbb_root_path; - - return parent::__construct($loader, $options); - } - - /** - * Get the list of enabled phpBB extensions - * - * Used in EVENT node - * - * @return array - */ - public function get_phpbb_extensions() - { - return $this->phpbb_extensions; - } - - /** - * Get phpBB config - * - * @return phpbb_config - */ - public function get_phpbb_config() - { - return $this->phpbb_config; - } - - /** - * Get the phpBB root path - * - * @return string - */ - public function get_phpbb_root_path() - { - return $this->phpbb_root_path; - } - - /** - * Get the namespace look up order - * - * @return array - */ - public function getNamespaceLookUpOrder() - { - return $this->namespace_look_up_order; - } - - /** - * Set the namespace look up order to load templates from - * - * @param array $namespace - * @return Twig_Environment - */ - public function setNamespaceLookUpOrder($namespace) - { - $this->namespace_look_up_order = $namespace; - - return $this; - } - - /** - * Loads a template by name. - * - * @param string $name The template name - * @param integer $index The index if it is an embedded template - * @return Twig_TemplateInterface A template instance representing the given template name - */ - public function loadTemplate($name, $index = null) - { - if (strpos($name, '@') === false) - { - foreach ($this->getNamespaceLookUpOrder() as $namespace) - { - try - { - if ($namespace === '__main__') - { - return parent::loadTemplate($name, $index); - } - - return parent::loadTemplate('@' . $namespace . '/' . $name, $index); - } - catch (Twig_Error_Loader $e) - { - } - } - - // We were unable to load any templates - throw $e; - } - else - { - return parent::loadTemplate($name, $index); - } - } -} diff --git a/phpBB/includes/template/twig/extension.php b/phpBB/includes/template/twig/extension.php deleted file mode 100644 index c279726434..0000000000 --- a/phpBB/includes/template/twig/extension.php +++ /dev/null @@ -1,188 +0,0 @@ -context = $context; - $this->user = $user; - } - - /** - * Get the name of this extension - * - * @return string - */ - public function getName() - { - return 'phpbb'; - } - - /** - * Returns the token parser instance to add to the existing list. - * - * @return array An array of Twig_TokenParser instances - */ - public function getTokenParsers() - { - return array( - new phpbb_template_twig_tokenparser_define, - new phpbb_template_twig_tokenparser_include, - new phpbb_template_twig_tokenparser_includejs, - new phpbb_template_twig_tokenparser_includecss, - new phpbb_template_twig_tokenparser_event, - new phpbb_template_twig_tokenparser_includephp, - new phpbb_template_twig_tokenparser_php, - ); - } - - /** - * Returns a list of filters to add to the existing list. - * - * @return array An array of filters - */ - public function getFilters() - { - return array( - new Twig_SimpleFilter('subset', array($this, 'loop_subset'), array('needs_environment' => true)), - new Twig_SimpleFilter('addslashes', 'addslashes'), - ); - } - - /** - * Returns a list of global functions to add to the existing list. - * - * @return array An array of global functions - */ - public function getFunctions() - { - return array( - new Twig_SimpleFunction('lang', array($this, 'lang')), - ); - } - - /** - * Returns a list of operators to add to the existing list. - * - * @return array An array of operators - */ - public function getOperators() - { - return array( - array( - '!' => array('precedence' => 50, 'class' => 'Twig_Node_Expression_Unary_Not'), - ), - array( - // precedence settings are copied from similar operators in Twig core extension - '||' => array('precedence' => 10, 'class' => 'Twig_Node_Expression_Binary_Or', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), - '&&' => array('precedence' => 15, 'class' => 'Twig_Node_Expression_Binary_And', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), - - 'eq' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Equal', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), - - 'ne' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_NotEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), - 'neq' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_NotEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), - '<>' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_NotEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), - - '===' => array('precedence' => 20, 'class' => 'phpbb_template_twig_node_expression_binary_equalequal', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), - '!==' => array('precedence' => 20, 'class' => 'phpbb_template_twig_node_expression_binary_notequalequal', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), - - 'gt' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Greater', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), - 'gte' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_GreaterEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), - 'ge' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_GreaterEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), - 'lt' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Less', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), - 'lte' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_LessEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), - 'le' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_LessEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), - - 'mod' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Mod', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), - ), - ); - } - - /** - * Grabs a subset of a loop - * - * @param Twig_Environment $env A Twig_Environment instance - * @param mixed $item A variable - * @param integer $start Start of the subset - * @param integer $end End of the subset - * @param Boolean $preserveKeys Whether to preserve key or not (when the input is an array) - * - * @return mixed The sliced variable - */ - function loop_subset(Twig_Environment $env, $item, $start, $end = null, $preserveKeys = false) - { - // We do almost the same thing as Twig's slice (array_slice), except when $end is positive - if ($end >= 1) - { - // When end is > 1, subset will end on the last item in an array with the specified $end - // This is different from slice in that it is the number we end on rather than the number - // of items to grab (length) - - // Start must always be the actual starting number for this calculation (not negative) - $start = ($start < 0) ? sizeof($item) + $start : $start; - $end = $end - $start; - } - - // We always include the last element (this was the past design) - $end = ($end == -1 || $end === null) ? null : $end + 1; - - return twig_slice($env, $item, $start, $end, $preserveKeys); - } - - /** - * Get output for a language variable (L_FOO, LA_FOO) - * - * This function checks to see if the language var was outputted to $context - * (e.g. in the ACP, L_TITLE) - * If not, we return the result of $user->lang() - * - * @param string $lang name - * @return string - */ - function lang() - { - $args = func_get_args(); - $key = $args[0]; - - $context = $this->context->get_data_ref(); - $context_vars = $context['.'][0]; - - if (isset($context_vars['L_' . $key])) - { - return $context_vars['L_' . $key]; - } - - // LA_ is transformed into lang(\'$1\')|addslashes, so we should not - // need to check for it - - return call_user_func_array(array($this->user, 'lang'), $args); - } -} diff --git a/phpBB/includes/template/twig/lexer.php b/phpBB/includes/template/twig/lexer.php deleted file mode 100644 index 46412ad048..0000000000 --- a/phpBB/includes/template/twig/lexer.php +++ /dev/null @@ -1,300 +0,0 @@ -fix_inline_variable_tokens(array( - 'DEFINE.+=', - 'INCLUDE', - 'INCLUDEPHP', - 'INCLUDEJS', - 'INCLUDECSS', - ), $code); - - // Fix our BEGIN statements - $code = $this->fix_begin_tokens($code); - - // Fix our IF tokens - $code = $this->fix_if_tokens($code); - - // Fix our DEFINE tokens - $code = $this->fix_define_tokens($code); - - // Replace all of our starting tokens, with Twig style, {% TOKEN %} - // This also strips outer parenthesis, becomes - $code = preg_replace('##', '{% $1 $2 %}', $code); - - // Replace all of our twig masks with Twig code (e.g. with {% block $1 %}) - $code = $this->replace_twig_tag_masks($code, $twig_tags); - - // Replace all of our language variables, {L_VARNAME}, with Twig style, {{ lang('NAME') }} - // Appends any filters after lang() - $code = preg_replace('#{L_([a-zA-Z0-9_\.]+)(\|[^}]+?)?}#', '{{ lang(\'$1\')$2 }}', $code); - - // Replace all of our escaped language variables, {LA_VARNAME}, with Twig style, {{ lang('NAME')|addslashes }} - // Appends any filters after lang(), but before addslashes - $code = preg_replace('#{LA_([a-zA-Z0-9_\.]+)(\|[^}]+?)?}#', '{{ lang(\'$1\')$2|addslashes }}', $code); - - // Replace all of our variables, {VARNAME}, with Twig style, {{ VARNAME }} - // Appends any filters - $code = preg_replace('#{([a-zA-Z0-9_\.]+)(\|[^}]+?)?}#', '{{ $1$2 }}', $code); - - return parent::tokenize($code, $filename); - } - - /** - * Fix tokens that may have inline variables - * - * E.g. "; - }; - - return preg_replace_callback('##', $callback, $code); - } - - /** - * Fix begin tokens (convert our BEGIN to Twig for) - * - * Not meant to be used outside of this context, public because the anonymous function calls this - * - * @param string $code - * @param array $parent_nodes (used in recursion) - * @return string - */ - public function fix_begin_tokens($code, $parent_nodes = array()) - { - // PHP 5.3 cannot use $this in an anonymous function, so use this as a work-around - $parent_class = $this; - $callback = function ($matches) use ($parent_class, $parent_nodes) - { - $name = $matches[1]; - $subset = trim(substr($matches[2], 1, -1)); // Remove parenthesis - $body = $matches[3]; - - // Is the designer wanting to call another loop in a loop? - // - // - // - // - // 'loop2' is actually on the same nesting level as 'loop' you assign - // variables to it with template->assign_block_vars('loop2', array(...)) - if (strpos($name, '!') === 0) - { - // Count the number if ! occurrences - $count = substr_count($name, '!'); - for ($i = 0; $i < $count; $i++) - { - array_pop($parent_nodes); - $name = substr($name, 1); - } - } - - // Remove all parent nodes, e.g. foo, bar from foo.bar.foobar.VAR - foreach ($parent_nodes as $node) - { - $body = preg_replace('#([^a-zA-Z0-9_])' . $node . '\.([a-zA-Z0-9_]+)\.#', '$1$2.', $body); - } - - // Add current node to list of parent nodes for child nodes - $parent_nodes[] = $name; - - // Recursive...fix any child nodes - $body = $parent_class->fix_begin_tokens($body, $parent_nodes); - - // Rename loopname vars (to prevent collisions, loop children are named (loop name)_loop_element) - $body = str_replace($name . '.', $name . '_loop_element.', $body); - - // Need the parent variable name - array_pop($parent_nodes); - $parent = (!empty($parent_nodes)) ? end($parent_nodes) . '_loop_element.' : ''; - - if ($subset !== '') - { - $subset = '|subset(' . $subset . ')'; - } - - // Turn into a Twig for loop, using (loop name)_loop_element for each child - return "{% for {$name}_loop_element in {$parent}{$name}{$subset} %}{$body}{% endfor %}"; - }; - - // Replace correctly, only needs to be done once - $code = str_replace('', '{% else %}', $code); - - return preg_replace_callback('#(.+?)#s', $callback, $code); - } - - /** - * Fix IF statements - * - * @param string $code - * @return string - */ - protected function fix_if_tokens($code) - { - $callback = function($matches) - { - // Replace $TEST with definition.TEST - $matches[1] = preg_replace('#\s\$([a-zA-Z_0-9]+)#', ' definition.$1', $matches[1]); - - // Replace .test with test|length - $matches[1] = preg_replace('#\s\.([a-zA-Z_0-9\.]+)#', ' $1|length', $matches[1]); - - return ''; - }; - - // Replace our "div by" with Twig's divisibleby (Twig does not like test names with spaces) - $code = preg_replace('# div by ([0-9]+)#', ' divisibleby($1)', $code); - - return preg_replace_callback('##', $callback, $code); - } - - /** - * Fix DEFINE statements and {$VARNAME} variables - * - * @param string $code - * @return string - */ - protected function fix_define_tokens($code) - { - /** - * Changing $VARNAME to definition.varname because set is only local - * context (e.g. DEFINE $TEST will only make $TEST available in current - * template and any child templates, but not any parent templates). - * - * DEFINE handles setting it properly to definition in its node, but the - * variables reading FROM it need to be altered to definition.VARNAME - * - * Setting up definition as a class in the array passed to Twig - * ($context) makes set definition.TEST available in the global context - */ - - // Replace #', '{% DEFINE $1 %}', $code); - - // Changing UNDEFINE NAME to DEFINE NAME = null to save from creating an extra token parser/node - $code = preg_replace('##', '{% DEFINE $1= null %}', $code); - - // Replace all of our variables, {$VARNAME}, with Twig style, {{ definition.VARNAME }} - $code = preg_replace('#{\$([a-zA-Z0-9_\.]+)}#', '{{ definition.$1 }}', $code); - - // Replace all of our variables, ~ $VARNAME ~, with Twig style, ~ definition.VARNAME ~ - $code = preg_replace('#~ \$([a-zA-Z0-9_\.]+) ~#', '~ definition.$1 ~', $code); - - return $code; - } - - /** - * Replace Twig tag masks with Twig tag calls - * - * E.g. with {% block foo %} - * - * @param string $code - * @param array $twig_tags All tags we want to create a mask for - * @return string - */ - protected function replace_twig_tag_masks($code, $twig_tags) - { - $callback = function ($matches) - { - $matches[1] = strtolower($matches[1]); - - return "{% {$matches[1]}{$matches[2]}%}"; - }; - - foreach ($twig_tags as &$tag) - { - $tag = strtoupper($tag); - } - - // twig_tags is an array of the twig tags, which are all lowercase, but we use all uppercase tags - $code = preg_replace_callback('##',$callback, $code); - - return $code; - } -} diff --git a/phpBB/includes/template/twig/node/define.php b/phpBB/includes/template/twig/node/define.php deleted file mode 100644 index fcb19cc773..0000000000 --- a/phpBB/includes/template/twig/node/define.php +++ /dev/null @@ -1,58 +0,0 @@ - $name, 'value' => $value), array('capture' => $capture, 'safe' => false), $lineno, $tag); - } - - /** - * Compiles the node to PHP. - * - * @param Twig_Compiler A Twig_Compiler instance - */ - public function compile(Twig_Compiler $compiler) - { - $compiler->addDebugInfo($this); - - if ($this->getAttribute('capture')) { - $compiler - ->write("ob_start();\n") - ->subcompile($this->getNode('value')) - ; - - $compiler->write("\$value = ('' === \$value = ob_get_clean()) ? '' : new Twig_Markup(\$value, \$this->env->getCharset());\n"); - } - else - { - $compiler - ->write("\$value = ") - ->subcompile($this->getNode('value')) - ->raw(";\n") - ; - } - - $compiler - ->write("\$context['definition']->set('") - ->raw($this->getNode('name')->getAttribute('name')) - ->raw("', \$value);\n") - ; - } -} diff --git a/phpBB/includes/template/twig/node/event.php b/phpBB/includes/template/twig/node/event.php deleted file mode 100644 index 971dea14fa..0000000000 --- a/phpBB/includes/template/twig/node/event.php +++ /dev/null @@ -1,79 +0,0 @@ -environment = $environment; - - parent::__construct(array('expr' => $expr), array(), $lineno, $tag); - } - - /** - * Compiles the node to PHP. - * - * @param Twig_Compiler A Twig_Compiler instance - */ - public function compile(Twig_Compiler $compiler) - { - $compiler->addDebugInfo($this); - - $location = $this->getNode('expr')->getAttribute('name'); - - foreach ($this->environment->get_phpbb_extensions() as $ext_namespace => $ext_path) - { - $ext_namespace = str_replace('/', '_', $ext_namespace); - - if (defined('DEBUG')) - { - // If debug mode is enabled, lets check for new/removed EVENT - // templates on page load rather than at compile. This is - // slower, but makes developing extensions easier (no need to - // purge the cache when a new event template file is added) - $compiler - ->write("if (\$this->env->getLoader()->exists('@{$ext_namespace}/{$location}.html')) {\n") - ->indent() - ; - } - - if (defined('DEBUG') || $this->environment->getLoader()->exists('@' . $ext_namespace . '/' . $location . '.html')) - { - $compiler - ->write("\$previous_look_up_order = \$this->env->getNamespaceLookUpOrder();\n") - - // We set the namespace lookup order to be this extension first, then the main path - ->write("\$this->env->setNamespaceLookUpOrder(array('{$ext_namespace}', '__main__'));\n") - ->write("\$this->env->loadTemplate('@{$ext_namespace}/{$location}.html')->display(\$context);\n") - ->write("\$this->env->setNamespaceLookUpOrder(\$previous_look_up_order);\n") - ; - } - - if (defined('DEBUG')) - { - $compiler - ->outdent() - ->write("}\n\n") - ; - } - } - } -} diff --git a/phpBB/includes/template/twig/node/expression/binary/equalequal.php b/phpBB/includes/template/twig/node/expression/binary/equalequal.php deleted file mode 100644 index 8ec2069114..0000000000 --- a/phpBB/includes/template/twig/node/expression/binary/equalequal.php +++ /dev/null @@ -1,25 +0,0 @@ -raw('==='); - } -} diff --git a/phpBB/includes/template/twig/node/expression/binary/notequalequal.php b/phpBB/includes/template/twig/node/expression/binary/notequalequal.php deleted file mode 100644 index 96f32c502e..0000000000 --- a/phpBB/includes/template/twig/node/expression/binary/notequalequal.php +++ /dev/null @@ -1,25 +0,0 @@ -raw('!=='); - } -} diff --git a/phpBB/includes/template/twig/node/include.php b/phpBB/includes/template/twig/node/include.php deleted file mode 100644 index 5c6ae1bbcf..0000000000 --- a/phpBB/includes/template/twig/node/include.php +++ /dev/null @@ -1,56 +0,0 @@ -addDebugInfo($this); - - $compiler - ->write("\$location = ") - ->subcompile($this->getNode('expr')) - ->raw(";\n") - ->write("\$namespace = false;\n") - ->write("if (strpos(\$location, '@') === 0) {\n") - ->indent() - ->write("\$namespace = substr(\$location, 1, strpos(\$location, '/') - 1);\n") - ->write("\$previous_look_up_order = \$this->env->getNamespaceLookUpOrder();\n") - - // We set the namespace lookup order to be this namespace first, then the main path - ->write("\$this->env->setNamespaceLookUpOrder(array(\$namespace, '__main__'));\n") - ->outdent() - ->write("}\n") - ; - - parent::compile($compiler); - - $compiler - ->write("if (\$namespace) {\n") - ->indent() - ->write("\$this->env->setNamespaceLookUpOrder(\$previous_look_up_order);\n") - ->outdent() - ->write("}\n") - ; - } -} diff --git a/phpBB/includes/template/twig/node/includeasset.php b/phpBB/includes/template/twig/node/includeasset.php deleted file mode 100644 index 990b1c984f..0000000000 --- a/phpBB/includes/template/twig/node/includeasset.php +++ /dev/null @@ -1,60 +0,0 @@ -environment = $environment; - - parent::__construct(array('expr' => $expr), array(), $lineno, $tag); - } - /** - * Compiles the node to PHP. - * - * @param Twig_Compiler A Twig_Compiler instance - */ - public function compile(Twig_Compiler $compiler) - { - $compiler->addDebugInfo($this); - - $config = $this->environment->get_phpbb_config(); - - $compiler - ->write("\$asset_file = ") - ->subcompile($this->getNode('expr')) - ->raw(";\n") - ->write("\$asset = new phpbb_template_asset(\$asset_file);\n") - ->write("if (substr(\$asset_file, 0, 2) !== './' && \$asset->is_relative()) {\n") - ->indent() - ->write("\$asset_path = \$asset->get_path();") - ->write("\$local_file = \$this->getEnvironment()->get_phpbb_root_path() . \$asset_path;\n") - ->write("if (!file_exists(\$local_file)) {\n") - ->indent() - ->write("\$local_file = \$this->getEnvironment()->getLoader()->getCacheKey(\$asset_path);\n") - ->write("\$asset->set_path(\$local_file, true);\n") - ->outdent() - ->write("\$asset->add_assets_version({$config['assets_version']});\n") - ->write("\$asset_file = \$asset->get_url();\n") - ->write("}\n") - ->outdent() - ->write("}\n") - ->write("\$context['definition']->append('{$this->get_definition_name()}', '") - ; - - $this->append_asset($compiler); - - $compiler - ->raw("\n');\n") - ; - } -} diff --git a/phpBB/includes/template/twig/node/includecss.php b/phpBB/includes/template/twig/node/includecss.php deleted file mode 100644 index 01fda44aad..0000000000 --- a/phpBB/includes/template/twig/node/includecss.php +++ /dev/null @@ -1,30 +0,0 @@ -raw("raw("\$asset_file . '\"") - ->raw(' rel="stylesheet" type="text/css" media="screen, projection" />') - ; - } -} diff --git a/phpBB/includes/template/twig/node/includejs.php b/phpBB/includes/template/twig/node/includejs.php deleted file mode 100644 index fdf2bea3ed..0000000000 --- a/phpBB/includes/template/twig/node/includejs.php +++ /dev/null @@ -1,32 +0,0 @@ -environment->get_phpbb_config(); - - $compiler - ->raw("\n") - ; - } -} diff --git a/phpBB/includes/template/twig/node/includephp.php b/phpBB/includes/template/twig/node/includephp.php deleted file mode 100644 index dbe54f0e1a..0000000000 --- a/phpBB/includes/template/twig/node/includephp.php +++ /dev/null @@ -1,91 +0,0 @@ -environment = $environment; - - parent::__construct(array('expr' => $expr), array('ignore_missing' => (Boolean) $ignoreMissing), $lineno, $tag); - } - - /** - * Compiles the node to PHP. - * - * @param Twig_Compiler A Twig_Compiler instance - */ - public function compile(Twig_Compiler $compiler) - { - $compiler->addDebugInfo($this); - - $config = $this->environment->get_phpbb_config(); - - if (!$config['tpl_allow_php']) - { - $compiler - ->write("// INCLUDEPHP Disabled\n") - ; - - return; - } - - if ($this->getAttribute('ignore_missing')) { - $compiler - ->write("try {\n") - ->indent() - ; - } - - $compiler - ->write("\$location = ") - ->subcompile($this->getNode('expr')) - ->raw(";\n") - ->write("if (phpbb_is_absolute(\$location)) {\n") - ->indent() - // Absolute path specified - ->write("require(\$location);\n") - ->outdent() - ->write("} else if (file_exists(\$this->getEnvironment()->get_phpbb_root_path() . \$location)) {\n") - ->indent() - // PHP file relative to phpbb_root_path - ->write("require(\$this->getEnvironment()->get_phpbb_root_path() . \$location);\n") - ->outdent() - ->write("} else {\n") - ->indent() - // Local path (behaves like INCLUDE) - ->write("require(\$this->getEnvironment()->getLoader()->getCacheKey(\$location));\n") - ->outdent() - ->write("}\n") - ; - - if ($this->getAttribute('ignore_missing')) { - $compiler - ->outdent() - ->write("} catch (Twig_Error_Loader \$e) {\n") - ->indent() - ->write("// ignore missing template\n") - ->outdent() - ->write("}\n\n") - ; - } - } -} diff --git a/phpBB/includes/template/twig/node/php.php b/phpBB/includes/template/twig/node/php.php deleted file mode 100644 index c11539ea7f..0000000000 --- a/phpBB/includes/template/twig/node/php.php +++ /dev/null @@ -1,55 +0,0 @@ -environment = $environment; - - parent::__construct(array('text' => $text), array(), $lineno, $tag); - } - - /** - * Compiles the node to PHP. - * - * @param Twig_Compiler A Twig_Compiler instance - */ - public function compile(Twig_Compiler $compiler) - { - $compiler->addDebugInfo($this); - - $config = $this->environment->get_phpbb_config(); - - if (!$config['tpl_allow_php']) - { - $compiler - ->write("// PHP Disabled\n") - ; - - return; - } - - $compiler - ->raw($this->getNode('text')->getAttribute('data')) - ; - } -} diff --git a/phpBB/includes/template/twig/tokenparser/define.php b/phpBB/includes/template/twig/tokenparser/define.php deleted file mode 100644 index 4ea15388c4..0000000000 --- a/phpBB/includes/template/twig/tokenparser/define.php +++ /dev/null @@ -1,66 +0,0 @@ -getLine(); - $stream = $this->parser->getStream(); - $name = $this->parser->getExpressionParser()->parseExpression(); - - $capture = false; - if ($stream->test(Twig_Token::OPERATOR_TYPE, '=')) { - $stream->next(); - $value = $this->parser->getExpressionParser()->parseExpression(); - - $stream->expect(Twig_Token::BLOCK_END_TYPE); - } else { - $capture = true; - - $stream->expect(Twig_Token::BLOCK_END_TYPE); - - $value = $this->parser->subparse(array($this, 'decideBlockEnd'), true); - $stream->expect(Twig_Token::BLOCK_END_TYPE); - } - - return new phpbb_template_twig_node_define($capture, $name, $value, $lineno, $this->getTag()); - } - - public function decideBlockEnd(Twig_Token $token) - { - return $token->test('ENDDEFINE'); - } - - /** - * Gets the tag name associated with this token parser. - * - * @return string The tag name - */ - public function getTag() - { - return 'DEFINE'; - } -} diff --git a/phpBB/includes/template/twig/tokenparser/event.php b/phpBB/includes/template/twig/tokenparser/event.php deleted file mode 100644 index e4dddd6dcc..0000000000 --- a/phpBB/includes/template/twig/tokenparser/event.php +++ /dev/null @@ -1,47 +0,0 @@ -parser->getExpressionParser()->parseExpression(); - - $stream = $this->parser->getStream(); - $stream->expect(Twig_Token::BLOCK_END_TYPE); - - return new phpbb_template_twig_node_event($expr, $this->parser->getEnvironment(), $token->getLine(), $this->getTag()); - } - - /** - * Gets the tag name associated with this token parser. - * - * @return string The tag name - */ - public function getTag() - { - return 'EVENT'; - } -} diff --git a/phpBB/includes/template/twig/tokenparser/include.php b/phpBB/includes/template/twig/tokenparser/include.php deleted file mode 100644 index 520f9fd1a0..0000000000 --- a/phpBB/includes/template/twig/tokenparser/include.php +++ /dev/null @@ -1,46 +0,0 @@ -parser->getExpressionParser()->parseExpression(); - - list($variables, $only, $ignoreMissing) = $this->parseArguments(); - - return new phpbb_template_twig_node_include($expr, $variables, $only, $ignoreMissing, $token->getLine(), $this->getTag()); - } - - /** - * Gets the tag name associated with this token parser. - * - * @return string The tag name - */ - public function getTag() - { - return 'INCLUDE'; - } -} diff --git a/phpBB/includes/template/twig/tokenparser/includecss.php b/phpBB/includes/template/twig/tokenparser/includecss.php deleted file mode 100644 index 6c24dda647..0000000000 --- a/phpBB/includes/template/twig/tokenparser/includecss.php +++ /dev/null @@ -1,38 +0,0 @@ -parser->getExpressionParser()->parseExpression(); - - $stream = $this->parser->getStream(); - $stream->expect(Twig_Token::BLOCK_END_TYPE); - - return new phpbb_template_twig_node_includecss($expr, $this->parser->getEnvironment(), $token->getLine(), $this->getTag()); - } - - /** - * Gets the tag name associated with this token parser. - * - * @return string The tag name - */ - public function getTag() - { - return 'INCLUDECSS'; - } -} diff --git a/phpBB/includes/template/twig/tokenparser/includejs.php b/phpBB/includes/template/twig/tokenparser/includejs.php deleted file mode 100644 index b02b2f89ba..0000000000 --- a/phpBB/includes/template/twig/tokenparser/includejs.php +++ /dev/null @@ -1,47 +0,0 @@ -parser->getExpressionParser()->parseExpression(); - - $stream = $this->parser->getStream(); - $stream->expect(Twig_Token::BLOCK_END_TYPE); - - return new phpbb_template_twig_node_includejs($expr, $this->parser->getEnvironment(), $token->getLine(), $this->getTag()); - } - - /** - * Gets the tag name associated with this token parser. - * - * @return string The tag name - */ - public function getTag() - { - return 'INCLUDEJS'; - } -} diff --git a/phpBB/includes/template/twig/tokenparser/includephp.php b/phpBB/includes/template/twig/tokenparser/includephp.php deleted file mode 100644 index 13fe6de8a6..0000000000 --- a/phpBB/includes/template/twig/tokenparser/includephp.php +++ /dev/null @@ -1,56 +0,0 @@ -parser->getExpressionParser()->parseExpression(); - - $stream = $this->parser->getStream(); - - $ignoreMissing = false; - if ($stream->test(Twig_Token::NAME_TYPE, 'ignore')) { - $stream->next(); - $stream->expect(Twig_Token::NAME_TYPE, 'missing'); - - $ignoreMissing = true; - } - - $stream->expect(Twig_Token::BLOCK_END_TYPE); - - return new phpbb_template_twig_node_includephp($expr, $this->parser->getEnvironment(), $ignoreMissing, $token->getLine(), $this->getTag()); - } - - /** - * Gets the tag name associated with this token parser. - * - * @return string The tag name - */ - public function getTag() - { - return 'INCLUDEPHP'; - } -} diff --git a/phpBB/includes/template/twig/tokenparser/php.php b/phpBB/includes/template/twig/tokenparser/php.php deleted file mode 100644 index 197980a59a..0000000000 --- a/phpBB/includes/template/twig/tokenparser/php.php +++ /dev/null @@ -1,55 +0,0 @@ -parser->getStream(); - - $stream->expect(Twig_Token::BLOCK_END_TYPE); - - $body = $this->parser->subparse(array($this, 'decideEnd'), true); - - $stream->expect(Twig_Token::BLOCK_END_TYPE); - - return new phpbb_template_twig_node_php($body, $this->parser->getEnvironment(), $token->getLine(), $this->getTag()); - } - - public function decideEnd(Twig_Token $token) - { - return $token->test('ENDPHP'); - } - - /** - * Gets the tag name associated with this token parser. - * - * @return string The tag name - */ - public function getTag() - { - return 'PHP'; - } -} diff --git a/phpBB/includes/template/twig/twig.php b/phpBB/includes/template/twig/twig.php deleted file mode 100644 index 92a37d1634..0000000000 --- a/phpBB/includes/template/twig/twig.php +++ /dev/null @@ -1,465 +0,0 @@ -phpbb_root_path = $phpbb_root_path; - $this->adm_relative_path = $adm_relative_path; - $this->php_ext = $php_ext; - $this->config = $config; - $this->user = $user; - $this->context = $context; - $this->extension_manager = $extension_manager; - - $this->cachepath = $phpbb_root_path . 'cache/twig/'; - - // Initiate the loader, __main__ namespace paths will be setup later in set_style_names() - $loader = new Twig_Loader_Filesystem(''); - - $this->twig = new phpbb_template_twig_environment( - $this->config, - ($this->extension_manager) ? $this->extension_manager->all_enabled() : array(), - $this->phpbb_root_path, - $loader, - array( - 'cache' => (defined('IN_INSTALL')) ? false : $this->cachepath, - 'debug' => defined('DEBUG'), - 'auto_reload' => (bool) $this->config['load_tplcompile'], - 'autoescape' => false, - ) - ); - - $this->twig->addExtension( - new phpbb_template_twig_extension( - $this->context, - $this->user - ) - ); - - $lexer = new phpbb_template_twig_lexer($this->twig); - - $this->twig->setLexer($lexer); - } - - /** - * Clear the cache - * - * @return phpbb_template - */ - public function clear_cache() - { - if (is_dir($this->cachepath)) - { - $this->twig->clearCacheFiles(); - } - - return $this; - } - - /** - * Sets the template filenames for handles. - * - * @param array $filename_array Should be a hash of handle => filename pairs. - * @return phpbb_template $this - */ - public function set_filenames(array $filename_array) - { - $this->filenames = array_merge($this->filenames, $filename_array); - - return $this; - } - - /** - * Sets the style names/paths corresponding to style hierarchy being compiled - * and/or rendered. - * - * @param array $style_names List of style names in inheritance tree order - * @param array $style_paths List of style paths in inheritance tree order - * @param bool $is_core True if the style names are the "core" styles for this page load - * Core means the main phpBB template files - * @return phpbb_template $this - */ - public function set_style_names(array $style_names, array $style_paths, $is_core = false) - { - $this->style_names = $style_names; - - // Set as __main__ namespace - $this->twig->getLoader()->setPaths($style_paths); - - // Core style namespace from phpbb_style::set_style() - if ($is_core) - { - $this->twig->getLoader()->setPaths($style_paths, 'core'); - } - - // Add admin namespace - if (is_dir($this->phpbb_root_path . $this->adm_relative_path . 'style/')) - { - $this->twig->getLoader()->setPaths($this->phpbb_root_path . $this->adm_relative_path . 'style/', 'admin'); - } - - // Add all namespaces for all extensions - if ($this->extension_manager instanceof phpbb_extension_manager) - { - $style_names[] = 'all'; - - foreach ($this->extension_manager->all_enabled() as $ext_namespace => $ext_path) - { - // namespaces cannot contain / - $namespace = str_replace('/', '_', $ext_namespace); - $paths = array(); - - foreach ($style_names as $style_name) - { - $ext_style_path = $ext_path . 'styles/' . $style_name . '/template'; - - if (is_dir($ext_style_path)) - { - $paths[] = $ext_style_path; - } - } - - $this->twig->getLoader()->setPaths($paths, $namespace); - } - } - - return $this; - } - - /** - * Clears all variables and blocks assigned to this template. - * - * @return phpbb_template $this - */ - public function destroy() - { - $this->context = array(); - - return $this; - } - - /** - * Reset/empty complete block - * - * @param string $blockname Name of block to destroy - * @return phpbb_template $this - */ - public function destroy_block_vars($blockname) - { - $this->context->destroy_block_vars($blockname); - - return $this; - } - - /** - * Display a template for provided handle. - * - * The template will be loaded and compiled, if necessary, first. - * - * This function calls hooks. - * - * @param string $handle Handle to display - * @return phpbb_template $this - */ - public function display($handle) - { - $result = $this->call_hook($handle, __FUNCTION__); - if ($result !== false) - { - return $result[0]; - } - - $this->twig->display($this->get_filename_from_handle($handle), $this->get_template_vars()); - - return $this; - } - - /** - * Calls hook if any is defined. - * - * @param string $handle Template handle being displayed. - * @param string $method Method name of the caller. - */ - protected function call_hook($handle, $method) - { - global $phpbb_hook; - - if (!empty($phpbb_hook) && $phpbb_hook->call_hook(array(__CLASS__, $method), $handle, $this)) - { - if ($phpbb_hook->hook_return(array(__CLASS__, $method))) - { - $result = $phpbb_hook->hook_return_result(array(__CLASS__, $method)); - return array($result); - } - } - - return false; - } - - /** - * Display the handle and assign the output to a template variable - * or return the compiled result. - * - * @param string $handle Handle to operate on - * @param string $template_var Template variable to assign compiled handle to - * @param bool $return_content If true return compiled handle, otherwise assign to $template_var - * @return phpbb_template|string if $return_content is true return string of the compiled handle, otherwise return $this - */ - public function assign_display($handle, $template_var = '', $return_content = true) - { - if ($return_content) - { - return $this->twig->render($this->get_filename_from_handle($handle), $this->get_template_vars()); - } - - $this->assign_var($template_var, $this->twig->render($this->get_filename_from_handle($handle, $this->get_template_vars()))); - - return $this; - } - - /** - * Assign key variable pairs from an array - * - * @param array $vararray A hash of variable name => value pairs - * @return phpbb_template $this - */ - public function assign_vars(array $vararray) - { - foreach ($vararray as $key => $val) - { - $this->assign_var($key, $val); - } - - return $this; - } - - /** - * Assign a single scalar value to a single key. - * - * Value can be a string, an integer or a boolean. - * - * @param string $varname Variable name - * @param string $varval Value to assign to variable - * @return phpbb_template $this - */ - public function assign_var($varname, $varval) - { - $this->context->assign_var($varname, $varval); - - return $this; - } - - /** - * Append text to the string value stored in a key. - * - * Text is appended using the string concatenation operator (.). - * - * @param string $varname Variable name - * @param string $varval Value to append to variable - * @return phpbb_template $this - */ - public function append_var($varname, $varval) - { - $this->context->append_var($varname, $varval); - - return $this; - } - - /** - * Assign key variable pairs from an array to a specified block - * @param string $blockname Name of block to assign $vararray to - * @param array $vararray A hash of variable name => value pairs - * @return phpbb_template $this - */ - public function assign_block_vars($blockname, array $vararray) - { - $this->context->assign_block_vars($blockname, $vararray); - - return $this; - } - - /** - * Change already assigned key variable pair (one-dimensional - single loop entry) - * - * An example of how to use this function: - * {@example alter_block_array.php} - * - * @param string $blockname the blockname, for example 'loop' - * @param array $vararray the var array to insert/add or merge - * @param mixed $key Key to search for - * - * array: KEY => VALUE [the key/value pair to search for within the loop to determine the correct position] - * - * int: Position [the position to change or insert at directly given] - * - * If key is false the position is set to 0 - * If key is true the position is set to the last entry - * - * @param string $mode Mode to execute (valid modes are 'insert' and 'change') - * - * If insert, the vararray is inserted at the given position (position counting from zero). - * If change, the current block gets merged with the vararray (resulting in new key/value pairs be added and existing keys be replaced by the new value). - * - * Since counting begins by zero, inserting at the last position will result in this array: array(vararray, last positioned array) - * and inserting at position 1 will result in this array: array(first positioned array, vararray, following vars) - * - * @return bool false on error, true on success - */ - public function alter_block_array($blockname, array $vararray, $key = false, $mode = 'insert') - { - return $this->context->alter_block_array($blockname, $vararray, $key, $mode); - } - - /** - * Get template vars in a format Twig will use (from the context) - * - * @return array - */ - public function get_template_vars() - { - $context_vars = $this->context->get_data_ref(); - - $vars = array_merge( - $context_vars['.'][0], // To get normal vars - $context_vars, // To get loops - array( - 'definition' => new phpbb_template_twig_definition(), - 'user' => $this->user, - ) - ); - - // cleanup - unset($vars['.']); - - return $vars; - } - - /** - * Get a filename from the handle - * - * @param string $handle - * @return string - */ - protected function get_filename_from_handle($handle) - { - return (isset($this->filenames[$handle])) ? $this->filenames[$handle] : $handle; - } - - /** - * Get path to template for handle (required for BBCode parser) - * - * @return string - */ - public function get_source_file_for_handle($handle) - { - return $this->twig->getLoader()->getCacheKey($this->get_filename_from_handle($handle)); - } -} diff --git a/phpBB/includes/tree/interface.php b/phpBB/includes/tree/interface.php deleted file mode 100644 index cc8aab2115..0000000000 --- a/phpBB/includes/tree/interface.php +++ /dev/null @@ -1,122 +0,0 @@ - down, > 0 => up - * @return bool True if the item was moved - */ - public function move($item_id, $delta); - - /** - * Move an item down by 1 - * - * @param int $item_id The item to be moved - * @return bool True if the item was moved - */ - public function move_down($item_id); - - /** - * Move an item up by 1 - * - * @param int $item_id The item to be moved - * @return bool True if the item was moved - */ - public function move_up($item_id); - - /** - * Moves all children of one item to another item - * - * If the new parent already has children, the new children are appended - * to the list. - * - * @param int $current_parent_id The current parent item - * @param int $new_parent_id The new parent item - * @return bool True if any items where moved - */ - public function move_children($current_parent_id, $new_parent_id); - - /** - * Change parent item - * - * Moves the item to the bottom of the new parent's list of children - * - * @param int $item_id The item to be moved - * @param int $new_parent_id The new parent item - * @return bool True if the parent was set successfully - */ - public function change_parent($item_id, $new_parent_id); - - /** - * Get all items that are either ancestors or descendants of the item - * - * @param int $item_id Id of the item to retrieve the ancestors/descendants from - * @param bool $order_asc Order the items ascendingly (most outer ancestor first) - * @param bool $include_item Should the item matching the given item id be included in the list as well - * @return array Array of items (containing all columns from the item table) - * ID => Item data - */ - public function get_path_and_subtree_data($item_id, $order_asc, $include_item); - - /** - * Get all of the item's ancestors - * - * @param int $item_id Id of the item to retrieve the ancestors from - * @param bool $order_asc Order the items ascendingly (most outer ancestor first) - * @param bool $include_item Should the item matching the given item id be included in the list as well - * @return array Array of items (containing all columns from the item table) - * ID => Item data - */ - public function get_path_data($item_id, $order_asc, $include_item); - - /** - * Get all of the item's descendants - * - * @param int $item_id Id of the item to retrieve the descendants from - * @param bool $order_asc Order the items ascendingly - * @param bool $include_item Should the item matching the given item id be included in the list as well - * @return array Array of items (containing all columns from the item table) - * ID => Item data - */ - public function get_subtree_data($item_id, $order_asc, $include_item); -} diff --git a/phpBB/includes/tree/nestedset.php b/phpBB/includes/tree/nestedset.php deleted file mode 100644 index 4d851a87a8..0000000000 --- a/phpBB/includes/tree/nestedset.php +++ /dev/null @@ -1,850 +0,0 @@ -db = $db; - $this->lock = $lock; - - $this->table_name = $table_name; - $this->message_prefix = $message_prefix; - $this->sql_where = $sql_where; - $this->item_basic_data = (!empty($item_basic_data)) ? $item_basic_data : array('*'); - - if (!empty($columns)) - { - foreach ($columns as $column => $name) - { - $column_name = 'column_' . $column; - $this->$column_name = $name; - } - } - } - - /** - * Returns additional sql where restrictions - * - * @param string $operator SQL operator that needs to be prepended to sql_where, - * if it is not empty. - * @param string $column_prefix Prefix that needs to be prepended to column names - * @return string Returns additional where statements to narrow down the tree, - * prefixed with operator and prepended column_prefix to column names - */ - public function get_sql_where($operator = 'AND', $column_prefix = '') - { - return (!$this->sql_where) ? '' : $operator . ' ' . sprintf($this->sql_where, $column_prefix); - } - - /** - * Acquires a lock on the item table - * - * @return bool True if the lock was acquired, false if it has been acquired previously - * - * @throws RuntimeException If the lock could not be acquired - */ - protected function acquire_lock() - { - if ($this->lock->owns_lock()) - { - return false; - } - - if (!$this->lock->acquire()) - { - throw new RuntimeException($this->message_prefix . 'LOCK_FAILED_ACQUIRE'); - } - - return true; - } - - /** - * @inheritdoc - */ - public function insert(array $additional_data) - { - $item_data = $this->reset_nestedset_values($additional_data); - - $sql = 'INSERT INTO ' . $this->table_name . ' ' . $this->db->sql_build_array('INSERT', $item_data); - $this->db->sql_query($sql); - - $item_data[$this->column_item_id] = (int) $this->db->sql_nextid(); - - return array_merge($item_data, $this->add_item_to_nestedset($item_data[$this->column_item_id])); - } - - /** - * Add an item which already has a database row at the end of the tree - * - * @param int $item_id The item to be added - * @return array Array with updated data, if the item was added successfully - * Empty array otherwise - */ - protected function add_item_to_nestedset($item_id) - { - $sql = 'SELECT MAX(' . $this->column_right_id . ') AS ' . $this->column_right_id . ' - FROM ' . $this->table_name . ' - ' . $this->get_sql_where('WHERE'); - $result = $this->db->sql_query($sql); - $current_max_right_id = (int) $this->db->sql_fetchfield($this->column_right_id); - $this->db->sql_freeresult($result); - - $update_item_data = array( - $this->column_parent_id => 0, - $this->column_left_id => $current_max_right_id + 1, - $this->column_right_id => $current_max_right_id + 2, - $this->column_item_parents => '', - ); - - $sql = 'UPDATE ' . $this->table_name . ' - SET ' . $this->db->sql_build_array('UPDATE', $update_item_data) . ' - WHERE ' . $this->column_item_id . ' = ' . (int) $item_id . ' - AND ' . $this->column_parent_id . ' = 0 - AND ' . $this->column_left_id . ' = 0 - AND ' . $this->column_right_id . ' = 0'; - $this->db->sql_query($sql); - - return ($this->db->sql_affectedrows() == 1) ? $update_item_data : array(); - } - - /** - * Remove an item from the tree without deleting it from the database - * - * Also removes all subitems from the tree without deleting them from the database either - * - * @param int $item_id The item to be deleted - * @return array Item ids that have been removed - */ - protected function remove_item_from_nestedset($item_id) - { - $item_id = (int) $item_id; - if (!$item_id) - { - throw new OutOfBoundsException($this->message_prefix . 'INVALID_ITEM'); - } - - $items = $this->get_subtree_data($item_id); - $item_ids = array_keys($items); - - if (empty($items) || !isset($items[$item_id])) - { - throw new OutOfBoundsException($this->message_prefix . 'INVALID_ITEM'); - } - - $this->remove_subset($item_ids, $items[$item_id]); - - return $item_ids; - } - - /** - * @inheritdoc - */ - public function delete($item_id) - { - $removed_items = $this->remove_item_from_nestedset($item_id); - - $sql = 'DELETE FROM ' . $this->table_name . ' - WHERE ' . $this->db->sql_in_set($this->column_item_id, $removed_items) . ' - ' . $this->get_sql_where('AND'); - $this->db->sql_query($sql); - - return $removed_items; - } - - /** - * @inheritdoc - */ - public function move($item_id, $delta) - { - if ($delta == 0) - { - return false; - } - - $this->acquire_lock(); - - $action = ($delta > 0) ? 'move_up' : 'move_down'; - $delta = abs($delta); - - // Keep $this->get_sql_where() here, to ensure we are in the right tree. - $sql = 'SELECT * - FROM ' . $this->table_name . ' - WHERE ' . $this->column_item_id . ' = ' . (int) $item_id . ' - ' . $this->get_sql_where(); - $result = $this->db->sql_query_limit($sql, $delta); - $item = $this->db->sql_fetchrow($result); - $this->db->sql_freeresult($result); - - if (!$item) - { - $this->lock->release(); - throw new OutOfBoundsException($this->message_prefix . 'INVALID_ITEM'); - } - - /** - * Fetch all the siblings between the item's current spot - * and where we want to move it to. If there are less than $delta - * siblings between the current spot and the target then the - * item will move as far as possible - */ - $sql = "SELECT {$this->column_item_id}, {$this->column_parent_id}, {$this->column_left_id}, {$this->column_right_id}, {$this->column_item_parents} - FROM " . $this->table_name . ' - WHERE ' . $this->column_parent_id . ' = ' . (int) $item[$this->column_parent_id] . ' - ' . $this->get_sql_where() . ' - AND '; - - if ($action == 'move_up') - { - $sql .= $this->column_right_id . ' < ' . (int) $item[$this->column_right_id] . ' ORDER BY ' . $this->column_right_id . ' DESC'; - } - else - { - $sql .= $this->column_left_id . ' > ' . (int) $item[$this->column_left_id] . ' ORDER BY ' . $this->column_left_id . ' ASC'; - } - - $result = $this->db->sql_query_limit($sql, $delta); - - $target = false; - while ($row = $this->db->sql_fetchrow($result)) - { - $target = $row; - } - $this->db->sql_freeresult($result); - - if (!$target) - { - $this->lock->release(); - // The item is already on top or bottom - return false; - } - - /** - * $left_id and $right_id define the scope of the items that are affected by the move. - * $diff_up and $diff_down are the values to substract or add to each item's left_id - * and right_id in order to move them up or down. - * $move_up_left and $move_up_right define the scope of the items that are moving - * up. Other items in the scope of ($left_id, $right_id) are considered to move down. - */ - if ($action == 'move_up') - { - $left_id = (int) $target[$this->column_left_id]; - $right_id = (int) $item[$this->column_right_id]; - - $diff_up = (int) $item[$this->column_left_id] - (int) $target[$this->column_left_id]; - $diff_down = (int) $item[$this->column_right_id] + 1 - (int) $item[$this->column_left_id]; - - $move_up_left = (int) $item[$this->column_left_id]; - $move_up_right = (int) $item[$this->column_right_id]; - } - else - { - $left_id = (int) $item[$this->column_left_id]; - $right_id = (int) $target[$this->column_right_id]; - - $diff_up = (int) $item[$this->column_right_id] + 1 - (int) $item[$this->column_left_id]; - $diff_down = (int) $target[$this->column_right_id] - (int) $item[$this->column_right_id]; - - $move_up_left = (int) $item[$this->column_right_id] + 1; - $move_up_right = (int) $target[$this->column_right_id]; - } - - // Now do the dirty job - $sql = 'UPDATE ' . $this->table_name . ' - SET ' . $this->column_left_id . ' = ' . $this->column_left_id . ' + CASE - WHEN ' . $this->column_left_id . " BETWEEN {$move_up_left} AND {$move_up_right} THEN -{$diff_up} - ELSE {$diff_down} - END, - " . $this->column_right_id . ' = ' . $this->column_right_id . ' + CASE - WHEN ' . $this->column_right_id . " BETWEEN {$move_up_left} AND {$move_up_right} THEN -{$diff_up} - ELSE {$diff_down} - END - WHERE - " . $this->column_left_id . " BETWEEN {$left_id} AND {$right_id} - AND " . $this->column_right_id . " BETWEEN {$left_id} AND {$right_id} - " . $this->get_sql_where(); - $this->db->sql_query($sql); - - $this->lock->release(); - - return true; - } - - /** - * @inheritdoc - */ - public function move_down($item_id) - { - return $this->move($item_id, -1); - } - - /** - * @inheritdoc - */ - public function move_up($item_id) - { - return $this->move($item_id, 1); - } - - /** - * @inheritdoc - */ - public function move_children($current_parent_id, $new_parent_id) - { - $current_parent_id = (int) $current_parent_id; - $new_parent_id = (int) $new_parent_id; - - if ($current_parent_id == $new_parent_id) - { - return false; - } - - if (!$current_parent_id) - { - throw new OutOfBoundsException($this->message_prefix . 'INVALID_ITEM'); - } - - $this->acquire_lock(); - - $item_data = $this->get_subtree_data($current_parent_id); - if (!isset($item_data[$current_parent_id])) - { - $this->lock->release(); - throw new OutOfBoundsException($this->message_prefix . 'INVALID_ITEM'); - } - - $current_parent = $item_data[$current_parent_id]; - unset($item_data[$current_parent_id]); - $move_items = array_keys($item_data); - - if (($current_parent[$this->column_right_id] - $current_parent[$this->column_left_id]) <= 1) - { - $this->lock->release(); - return false; - } - - if (in_array($new_parent_id, $move_items)) - { - $this->lock->release(); - throw new OutOfBoundsException($this->message_prefix . 'INVALID_PARENT'); - } - - $diff = sizeof($move_items) * 2; - $sql_exclude_moved_items = $this->db->sql_in_set($this->column_item_id, $move_items, true); - - $this->db->sql_transaction('begin'); - - $this->remove_subset($move_items, $current_parent, false, true); - - if ($new_parent_id) - { - // Retrieve new-parent again, it may have been changed... - $sql = 'SELECT * - FROM ' . $this->table_name . ' - WHERE ' . $this->column_item_id . ' = ' . $new_parent_id; - $result = $this->db->sql_query($sql); - $new_parent = $this->db->sql_fetchrow($result); - $this->db->sql_freeresult($result); - - if (!$new_parent) - { - $this->db->sql_transaction('rollback'); - $this->lock->release(); - throw new OutOfBoundsException($this->message_prefix . 'INVALID_PARENT'); - } - - $new_right_id = $this->prepare_adding_subset($move_items, $new_parent, true); - - if ($new_right_id > $current_parent[$this->column_right_id]) - { - $diff = ' + ' . ($new_right_id - $current_parent[$this->column_right_id]); - } - else - { - $diff = ' - ' . abs($new_right_id - $current_parent[$this->column_right_id]); - } - } - else - { - $sql = 'SELECT MAX(' . $this->column_right_id . ') AS ' . $this->column_right_id . ' - FROM ' . $this->table_name . ' - WHERE ' . $sql_exclude_moved_items . ' - ' . $this->get_sql_where('AND'); - $result = $this->db->sql_query($sql); - $row = $this->db->sql_fetchrow($result); - $this->db->sql_freeresult($result); - - $diff = ' + ' . ($row[$this->column_right_id] - $current_parent[$this->column_left_id]); - } - - $sql = 'UPDATE ' . $this->table_name . ' - SET ' . $this->column_left_id . ' = ' . $this->column_left_id . $diff . ', - ' . $this->column_right_id . ' = ' . $this->column_right_id . $diff . ', - ' . $this->column_parent_id . ' = ' . $this->db->sql_case($this->column_parent_id . ' = ' . $current_parent_id, $new_parent_id, $this->column_parent_id) . ', - ' . $this->column_item_parents . " = '' - WHERE " . $this->db->sql_in_set($this->column_item_id, $move_items) . ' - ' . $this->get_sql_where('AND'); - $this->db->sql_query($sql); - - $this->db->sql_transaction('commit'); - $this->lock->release(); - - return true; - } - - /** - * @inheritdoc - */ - public function change_parent($item_id, $new_parent_id) - { - $item_id = (int) $item_id; - $new_parent_id = (int) $new_parent_id; - - if ($item_id == $new_parent_id) - { - return false; - } - - if (!$item_id) - { - throw new OutOfBoundsException($this->message_prefix . 'INVALID_ITEM'); - } - - $this->acquire_lock(); - - $item_data = $this->get_subtree_data($item_id); - if (!isset($item_data[$item_id])) - { - $this->lock->release(); - throw new OutOfBoundsException($this->message_prefix . 'INVALID_ITEM'); - } - - $item = $item_data[$item_id]; - $move_items = array_keys($item_data); - - if (in_array($new_parent_id, $move_items)) - { - $this->lock->release(); - throw new OutOfBoundsException($this->message_prefix . 'INVALID_PARENT'); - } - - $diff = sizeof($move_items) * 2; - $sql_exclude_moved_items = $this->db->sql_in_set($this->column_item_id, $move_items, true); - - $this->db->sql_transaction('begin'); - - $this->remove_subset($move_items, $item, false, true); - - if ($new_parent_id) - { - // Retrieve new-parent again, it may have been changed... - $sql = 'SELECT * - FROM ' . $this->table_name . ' - WHERE ' . $this->column_item_id . ' = ' . $new_parent_id; - $result = $this->db->sql_query($sql); - $new_parent = $this->db->sql_fetchrow($result); - $this->db->sql_freeresult($result); - - if (!$new_parent) - { - $this->db->sql_transaction('rollback'); - $this->lock->release(); - throw new OutOfBoundsException($this->message_prefix . 'INVALID_PARENT'); - } - - $new_right_id = $this->prepare_adding_subset($move_items, $new_parent, true); - - if ($new_right_id > (int) $item[$this->column_right_id]) - { - $diff = ' + ' . ($new_right_id - (int) $item[$this->column_right_id] - 1); - } - else - { - $diff = ' - ' . abs($new_right_id - (int) $item[$this->column_right_id] - 1); - } - } - else - { - $sql = 'SELECT MAX(' . $this->column_right_id . ') AS ' . $this->column_right_id . ' - FROM ' . $this->table_name . ' - WHERE ' . $sql_exclude_moved_items . ' - ' . $this->get_sql_where('AND'); - $result = $this->db->sql_query($sql); - $row = $this->db->sql_fetchrow($result); - $this->db->sql_freeresult($result); - - $diff = ' + ' . ($row[$this->column_right_id] - (int) $item[$this->column_left_id] + 1); - } - - $sql = 'UPDATE ' . $this->table_name . ' - SET ' . $this->column_left_id . ' = ' . $this->column_left_id . $diff . ', - ' . $this->column_right_id . ' = ' . $this->column_right_id . $diff . ', - ' . $this->column_parent_id . ' = ' . $this->db->sql_case($this->column_item_id . ' = ' . $item_id, $new_parent_id, $this->column_parent_id) . ', - ' . $this->column_item_parents . " = '' - WHERE " . $this->db->sql_in_set($this->column_item_id, $move_items) . ' - ' . $this->get_sql_where('AND'); - $this->db->sql_query($sql); - - $this->db->sql_transaction('commit'); - $this->lock->release(); - - return true; - } - - /** - * @inheritdoc - */ - public function get_path_and_subtree_data($item_id, $order_asc = true, $include_item = true) - { - $condition = 'i2.' . $this->column_left_id . ' BETWEEN i1.' . $this->column_left_id . ' AND i1.' . $this->column_right_id . ' - OR i1.' . $this->column_left_id . ' BETWEEN i2.' . $this->column_left_id . ' AND i2.' . $this->column_right_id; - - return $this->get_set_of_nodes_data($item_id, $condition, $order_asc, $include_item); - } - - /** - * @inheritdoc - */ - public function get_path_data($item_id, $order_asc = true, $include_item = true) - { - $condition = 'i1.' . $this->column_left_id . ' BETWEEN i2.' . $this->column_left_id . ' AND i2.' . $this->column_right_id . ''; - - return $this->get_set_of_nodes_data($item_id, $condition, $order_asc, $include_item); - } - - /** - * @inheritdoc - */ - public function get_subtree_data($item_id, $order_asc = true, $include_item = true) - { - $condition = 'i2.' . $this->column_left_id . ' BETWEEN i1.' . $this->column_left_id . ' AND i1.' . $this->column_right_id . ''; - - return $this->get_set_of_nodes_data($item_id, $condition, $order_asc, $include_item); - } - - /** - * Get items that are related to the given item by the condition - * - * @param int $item_id Id of the item to retrieve the node set from - * @param string $condition Query string restricting the item list - * @param bool $order_asc Order the items ascending by their left_id - * @param bool $include_item Should the item matching the given item id be included in the list as well - * @return array Array of items (containing all columns from the item table) - * ID => Item data - */ - protected function get_set_of_nodes_data($item_id, $condition, $order_asc = true, $include_item = true) - { - $rows = array(); - - $sql = 'SELECT i2.* - FROM ' . $this->table_name . ' i1 - LEFT JOIN ' . $this->table_name . " i2 - ON (($condition) " . $this->get_sql_where('AND', 'i2.') . ') - WHERE i1.' . $this->column_item_id . ' = ' . (int) $item_id . ' - ' . $this->get_sql_where('AND', 'i1.') . ' - ORDER BY i2.' . $this->column_left_id . ' ' . ($order_asc ? 'ASC' : 'DESC'); - $result = $this->db->sql_query($sql); - - while ($row = $this->db->sql_fetchrow($result)) - { - if (!$include_item && $item_id == $row[$this->column_item_id]) - { - continue; - } - - $rows[(int) $row[$this->column_item_id]] = $row; - } - $this->db->sql_freeresult($result); - - return $rows; - } - - /** - * Get basic data of all parent items - * - * Basic data is defined in the $item_basic_data property. - * Data is cached in the item_parents column in the item table - * - * @param array $item The item to get the path from - * @return array Array of items (containing basic columns from the item table) - * ID => Item data - */ - public function get_path_basic_data(array $item) - { - $parents = array(); - if ($item[$this->column_parent_id]) - { - if (!$item[$this->column_item_parents]) - { - $sql = 'SELECT ' . implode(', ', $this->item_basic_data) . ' - FROM ' . $this->table_name . ' - WHERE ' . $this->column_left_id . ' < ' . (int) $item[$this->column_left_id] . ' - AND ' . $this->column_right_id . ' > ' . (int) $item[$this->column_right_id] . ' - ' . $this->get_sql_where('AND') . ' - ORDER BY ' . $this->column_left_id . ' ASC'; - $result = $this->db->sql_query($sql); - - while ($row = $this->db->sql_fetchrow($result)) - { - $parents[$row[$this->column_item_id]] = $row; - } - $this->db->sql_freeresult($result); - - $item_parents = serialize($parents); - - $sql = 'UPDATE ' . $this->table_name . ' - SET ' . $this->column_item_parents . " = '" . $this->db->sql_escape($item_parents) . "' - WHERE " . $this->column_parent_id . ' = ' . (int) $item[$this->column_parent_id]; - $this->db->sql_query($sql); - } - else - { - $parents = unserialize($item[$this->column_item_parents]); - } - } - - return $parents; - } - - /** - * Remove a subset from the nested set - * - * @param array $subset_items Subset of items to remove - * @param array $bounding_item Item containing the right bound of the subset - * @param bool $set_subset_zero Should the parent, left and right id of the items be set to 0, or kept unchanged? - * In case of removing an item from the tree, we should the values to 0 - * In case of moving an item, we shouldkeep the original values, in order to allow "+ diff" later - * @return null - */ - protected function remove_subset(array $subset_items, array $bounding_item, $set_subset_zero = true) - { - $acquired_new_lock = $this->acquire_lock(); - - $diff = sizeof($subset_items) * 2; - $sql_subset_items = $this->db->sql_in_set($this->column_item_id, $subset_items); - $sql_not_subset_items = $this->db->sql_in_set($this->column_item_id, $subset_items, true); - - $sql_is_parent = $this->column_left_id . ' <= ' . (int) $bounding_item[$this->column_right_id] . ' - AND ' . $this->column_right_id . ' >= ' . (int) $bounding_item[$this->column_right_id]; - - $sql_is_right = $this->column_left_id . ' > ' . (int) $bounding_item[$this->column_right_id]; - - $set_left_id = $this->db->sql_case($sql_is_right, $this->column_left_id . ' - ' . $diff, $this->column_left_id); - $set_right_id = $this->db->sql_case($sql_is_parent . ' OR ' . $sql_is_right, $this->column_right_id . ' - ' . $diff, $this->column_right_id); - - if ($set_subset_zero) - { - $set_left_id = $this->db->sql_case($sql_subset_items, 0, $set_left_id); - $set_right_id = $this->db->sql_case($sql_subset_items, 0, $set_right_id); - } - - $sql = 'UPDATE ' . $this->table_name . ' - SET ' . (($set_subset_zero) ? $this->column_parent_id . ' = ' . $this->db->sql_case($sql_subset_items, 0, $this->column_parent_id) . ',' : '') . ' - ' . $this->column_left_id . ' = ' . $set_left_id . ', - ' . $this->column_right_id . ' = ' . $set_right_id . ' - ' . ((!$set_subset_zero) ? ' WHERE ' . $sql_not_subset_items . ' ' . $this->get_sql_where('AND') : $this->get_sql_where('WHERE')); - $this->db->sql_query($sql); - - if ($acquired_new_lock) - { - $this->lock->release(); - } - } - - /** - * Prepare adding a subset to the nested set - * - * @param array $subset_items Subset of items to add - * @param array $new_parent Item containing the right bound of the new parent - * @return int New right id of the parent item - */ - protected function prepare_adding_subset(array $subset_items, array $new_parent) - { - $diff = sizeof($subset_items) * 2; - $sql_not_subset_items = $this->db->sql_in_set($this->column_item_id, $subset_items, true); - - $set_left_id = $this->db->sql_case($this->column_left_id . ' > ' . (int) $new_parent[$this->column_right_id], $this->column_left_id . ' + ' . $diff, $this->column_left_id); - $set_right_id = $this->db->sql_case($this->column_right_id . ' >= ' . (int) $new_parent[$this->column_right_id], $this->column_right_id . ' + ' . $diff, $this->column_right_id); - - $sql = 'UPDATE ' . $this->table_name . ' - SET ' . $this->column_left_id . ' = ' . $set_left_id . ', - ' . $this->column_right_id . ' = ' . $set_right_id . ' - WHERE ' . $sql_not_subset_items . ' - ' . $this->get_sql_where('AND'); - $this->db->sql_query($sql); - - return $new_parent[$this->column_right_id] + $diff; - } - - /** - * Resets values required for the nested set system - * - * @param array $item Original item data - * @return array Original item data + nested set defaults - */ - protected function reset_nestedset_values(array $item) - { - $item_data = array_merge($item, array( - $this->column_parent_id => 0, - $this->column_left_id => 0, - $this->column_right_id => 0, - $this->column_item_parents => '', - )); - - unset($item_data[$this->column_item_id]); - - return $item_data; - } - - /** - * Regenerate left/right ids from parent/child relationship - * - * This method regenerates the left/right ids for the tree based on - * the parent/child relations. This function executes three queries per - * item, so it should only be called, when the set has one of the following - * problems: - * - The set has a duplicated value inside the left/right id chain - * - The set has a missing value inside the left/right id chain - * - The set has items that do not have a left/right id set - * - * When regenerating the items, the items are sorted by parent id and their - * current left id, so the current child/parent relationships are kept - * and running the function on a working set will not change the order. - * - * @param int $new_id First left_id to be used (should start with 1) - * @param int $parent_id parent_id of the current set (default = 0) - * @param bool $reset_ids Should we reset all left_id/right_id on the first call? - * @return int $new_id The next left_id/right_id that should be used - */ - public function regenerate_left_right_ids($new_id, $parent_id = 0, $reset_ids = false) - { - if ($acquired_new_lock = $this->acquire_lock()) - { - $this->db->sql_transaction('begin'); - - if (!$reset_ids) - { - $sql = 'UPDATE ' . $this->table_name . ' - SET ' . $this->column_item_parents . " = '' - " . $this->get_sql_where('WHERE'); - $this->db->sql_query($sql); - } - } - - if ($reset_ids) - { - $sql = 'UPDATE ' . $this->table_name . ' - SET ' . $this->db->sql_build_array('UPDATE', array( - $this->column_left_id => 0, - $this->column_right_id => 0, - $this->column_item_parents => '', - )) . ' - ' . $this->get_sql_where('WHERE'); - $this->db->sql_query($sql); - } - - $sql = 'SELECT * - FROM ' . $this->table_name . ' - WHERE ' . $this->column_parent_id . ' = ' . (int) $parent_id . ' - ' . $this->get_sql_where('AND') . ' - ORDER BY ' . $this->column_left_id . ', ' . $this->column_item_id . ' ASC'; - $result = $this->db->sql_query($sql); - while ($row = $this->db->sql_fetchrow($result)) - { - // First we update the left_id for this module - if ($row[$this->column_left_id] != $new_id) - { - $sql = 'UPDATE ' . $this->table_name . ' - SET ' . $this->db->sql_build_array('UPDATE', array($this->column_left_id => $new_id)) . ' - WHERE ' . $this->column_item_id . ' = ' . (int) $row[$this->column_item_id]; - $this->db->sql_query($sql); - } - $new_id++; - - // Then we go through any children and update their left/right id's - $new_id = $this->regenerate_left_right_ids($new_id, $row[$this->column_item_id]); - - // Then we come back and update the right_id for this module - if ($row[$this->column_right_id] != $new_id) - { - $sql = 'UPDATE ' . $this->table_name . ' - SET ' . $this->db->sql_build_array('UPDATE', array($this->column_right_id => $new_id)) . ' - WHERE ' . $this->column_item_id . ' = ' . (int) $row[$this->column_item_id]; - $this->db->sql_query($sql); - } - $new_id++; - } - $this->db->sql_freeresult($result); - - if ($acquired_new_lock) - { - $this->db->sql_transaction('commit'); - $this->lock->release(); - } - - return $new_id; - } -} diff --git a/phpBB/includes/tree/nestedset_forum.php b/phpBB/includes/tree/nestedset_forum.php deleted file mode 100644 index ff09ef55d0..0000000000 --- a/phpBB/includes/tree/nestedset_forum.php +++ /dev/null @@ -1,46 +0,0 @@ - 'forum_id', - 'item_parents' => 'forum_parents', - ) - ); - } -} diff --git a/phpBB/includes/user.php b/phpBB/includes/user.php deleted file mode 100644 index 5530fe3f03..0000000000 --- a/phpBB/includes/user.php +++ /dev/null @@ -1,857 +0,0 @@ - 0, 'viewflash' => 1, 'viewsmilies' => 2, 'viewsigs' => 3, 'viewavatars' => 4, 'viewcensors' => 5, 'attachsig' => 6, 'bbcode' => 8, 'smilies' => 9, 'popuppm' => 10, 'sig_bbcode' => 15, 'sig_smilies' => 16, 'sig_links' => 17); - - /** - * Constructor to set the lang path - */ - function __construct() - { - global $phpbb_root_path; - - $this->lang_path = $phpbb_root_path . 'language/'; - } - - /** - * Function to set custom language path (able to use directory outside of phpBB) - * - * @param string $lang_path New language path used. - * @access public - */ - function set_custom_lang_path($lang_path) - { - $this->lang_path = $lang_path; - - if (substr($this->lang_path, -1) != '/') - { - $this->lang_path .= '/'; - } - } - - /** - * Setup basic user-specific items (style, language, ...) - */ - function setup($lang_set = false, $style_id = false) - { - global $db, $phpbb_style, $template, $config, $auth, $phpEx, $phpbb_root_path, $cache; - global $phpbb_dispatcher; - - if ($this->data['user_id'] != ANONYMOUS) - { - $user_lang_name = (file_exists($this->lang_path . $this->data['user_lang'] . "/common.$phpEx")) ? $this->data['user_lang'] : basename($config['default_lang']); - $user_date_format = $this->data['user_dateformat']; - $user_timezone = $this->data['user_timezone']; - } - else - { - $user_lang_name = basename($config['default_lang']); - $user_date_format = $config['default_dateformat']; - $user_timezone = $config['board_timezone']; - - /** - * If a guest user is surfing, we try to guess his/her language first by obtaining the browser language - * If re-enabled we need to make sure only those languages installed are checked - * Commented out so we do not loose the code. - - if ($request->header('Accept-Language')) - { - $accept_lang_ary = explode(',', $request->header('Accept-Language')); - - foreach ($accept_lang_ary as $accept_lang) - { - // Set correct format ... guess full xx_YY form - $accept_lang = substr($accept_lang, 0, 2) . '_' . strtoupper(substr($accept_lang, 3, 2)); - $accept_lang = basename($accept_lang); - - if (file_exists($this->lang_path . $accept_lang . "/common.$phpEx")) - { - $user_lang_name = $config['default_lang'] = $accept_lang; - break; - } - else - { - // No match on xx_YY so try xx - $accept_lang = substr($accept_lang, 0, 2); - $accept_lang = basename($accept_lang); - - if (file_exists($this->lang_path . $accept_lang . "/common.$phpEx")) - { - $user_lang_name = $config['default_lang'] = $accept_lang; - break; - } - } - } - } - */ - } - - $user_data = $this->data; - - /** - * Event to load language files and modify user data on every page - * - * @event core.user_setup - * @var array user_data Array with user's data row - * @var string user_lang_name Basename of the user's langauge - * @var string user_date_format User's date/time format - * @var string user_timezone User's timezone, should be one of - * http://www.php.net/manual/en/timezones.php - * @var mixed lang_set String or array of language files - * @var mixed style_id Style we are going to display - * @since 3.1-A1 - */ - $vars = array('user_data', 'user_lang_name', 'user_date_format', 'user_timezone', 'lang_set', 'style_id'); - extract($phpbb_dispatcher->trigger_event('core.user_setup', compact($vars))); - - $this->data = $user_data; - $this->lang_name = $user_lang_name; - $this->date_format = $user_date_format; - - try - { - $this->timezone = new DateTimeZone($user_timezone); - } - catch (Exception $e) - { - // If the timezone the user has selected is invalid, we fall back to UTC. - $this->timezone = new DateTimeZone('UTC'); - } - - // We include common language file here to not load it every time a custom language file is included - $lang = &$this->lang; - - // Do not suppress error if in DEBUG mode - $include_result = (defined('DEBUG')) ? (include $this->lang_path . $this->lang_name . "/common.$phpEx") : (@include $this->lang_path . $this->lang_name . "/common.$phpEx"); - - if ($include_result === false) - { - die('Language file ' . $this->lang_path . $this->lang_name . "/common.$phpEx" . " couldn't be opened."); - } - - $this->add_lang($lang_set); - unset($lang_set); - - $style_request = request_var('style', 0); - if ($style_request && $auth->acl_get('a_styles') && !defined('ADMIN_START')) - { - global $SID, $_EXTRA_URL; - - $style_id = $style_request; - $SID .= '&style=' . $style_id; - $_EXTRA_URL = array('style=' . $style_id); - } - else - { - // Set up style - $style_id = ($style_id) ? $style_id : ((!$config['override_user_style']) ? $this->data['user_style'] : $config['default_style']); - } - - $sql = 'SELECT * - FROM ' . STYLES_TABLE . " s - WHERE s.style_id = $style_id"; - $result = $db->sql_query($sql, 3600); - $this->style = $db->sql_fetchrow($result); - $db->sql_freeresult($result); - - // User has wrong style - if (!$this->style && $style_id == $this->data['user_style']) - { - $style_id = $this->data['user_style'] = $config['default_style']; - - $sql = 'UPDATE ' . USERS_TABLE . " - SET user_style = $style_id - WHERE user_id = {$this->data['user_id']}"; - $db->sql_query($sql); - - $sql = 'SELECT * - FROM ' . STYLES_TABLE . " s - WHERE s.style_id = $style_id"; - $result = $db->sql_query($sql, 3600); - $this->style = $db->sql_fetchrow($result); - $db->sql_freeresult($result); - } - - if (!$this->style) - { - trigger_error('NO_STYLE_DATA', E_USER_ERROR); - } - - // Now parse the cfg file and cache it - $parsed_items = $cache->obtain_cfg_items($this->style); - - $check_for = array( - 'pagination_sep' => (string) ', ' - ); - - foreach ($check_for as $key => $default_value) - { - $this->style[$key] = (isset($parsed_items[$key])) ? $parsed_items[$key] : $default_value; - settype($this->style[$key], gettype($default_value)); - - if (is_string($default_value)) - { - $this->style[$key] = htmlspecialchars($this->style[$key]); - } - } - - $phpbb_style->set_style(); - - $this->img_lang = $this->lang_name; - - // Call phpbb_user_session_handler() in case external application want to "bend" some variables or replace classes... - // After calling it we continue script execution... - phpbb_user_session_handler(); - - // If this function got called from the error handler we are finished here. - if (defined('IN_ERROR_HANDLER')) - { - return; - } - - // Disable board if the install/ directory is still present - // For the brave development army we do not care about this, else we need to comment out this everytime we develop locally - if (!defined('DEBUG') && !defined('ADMIN_START') && !defined('IN_INSTALL') && !defined('IN_LOGIN') && file_exists($phpbb_root_path . 'install') && !is_file($phpbb_root_path . 'install')) - { - // Adjust the message slightly according to the permissions - if ($auth->acl_gets('a_', 'm_') || $auth->acl_getf_global('m_')) - { - $message = 'REMOVE_INSTALL'; - } - else - { - $message = (!empty($config['board_disable_msg'])) ? $config['board_disable_msg'] : 'BOARD_DISABLE'; - } - trigger_error($message); - } - - // Is board disabled and user not an admin or moderator? - if ($config['board_disable'] && !defined('IN_LOGIN') && !$auth->acl_gets('a_', 'm_') && !$auth->acl_getf_global('m_')) - { - if ($this->data['is_bot']) - { - send_status_line(503, 'Service Unavailable'); - } - - $message = (!empty($config['board_disable_msg'])) ? $config['board_disable_msg'] : 'BOARD_DISABLE'; - trigger_error($message); - } - - // Is load exceeded? - if ($config['limit_load'] && $this->load !== false) - { - if ($this->load > floatval($config['limit_load']) && !defined('IN_LOGIN') && !defined('IN_ADMIN')) - { - // Set board disabled to true to let the admins/mods get the proper notification - $config['board_disable'] = '1'; - - if (!$auth->acl_gets('a_', 'm_') && !$auth->acl_getf_global('m_')) - { - if ($this->data['is_bot']) - { - send_status_line(503, 'Service Unavailable'); - } - trigger_error('BOARD_UNAVAILABLE'); - } - } - } - - if (isset($this->data['session_viewonline'])) - { - // Make sure the user is able to hide his session - if (!$this->data['session_viewonline']) - { - // Reset online status if not allowed to hide the session... - if (!$auth->acl_get('u_hideonline')) - { - $sql = 'UPDATE ' . SESSIONS_TABLE . ' - SET session_viewonline = 1 - WHERE session_user_id = ' . $this->data['user_id']; - $db->sql_query($sql); - $this->data['session_viewonline'] = 1; - } - } - else if (!$this->data['user_allow_viewonline']) - { - // the user wants to hide and is allowed to -> cloaking device on. - if ($auth->acl_get('u_hideonline')) - { - $sql = 'UPDATE ' . SESSIONS_TABLE . ' - SET session_viewonline = 0 - WHERE session_user_id = ' . $this->data['user_id']; - $db->sql_query($sql); - $this->data['session_viewonline'] = 0; - } - } - } - - - // Does the user need to change their password? If so, redirect to the - // ucp profile reg_details page ... of course do not redirect if we're already in the ucp - if (!defined('IN_ADMIN') && !defined('ADMIN_START') && $config['chg_passforce'] && !empty($this->data['is_registered']) && $auth->acl_get('u_chgpasswd') && $this->data['user_passchg'] < time() - ($config['chg_passforce'] * 86400)) - { - if (strpos($this->page['query_string'], 'mode=reg_details') === false && $this->page['page_name'] != "ucp.$phpEx") - { - redirect(append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=profile&mode=reg_details')); - } - } - - return; - } - - /** - * More advanced language substitution - * Function to mimic sprintf() with the possibility of using phpBB's language system to substitute nullar/singular/plural forms. - * Params are the language key and the parameters to be substituted. - * This function/functionality is inspired by SHS` and Ashe. - * - * Example call: $user->lang('NUM_POSTS_IN_QUEUE', 1); - * - * If the first parameter is an array, the elements are used as keys and subkeys to get the language entry: - * Example: $user->lang(array('datetime', 'AGO'), 1) uses $user->lang['datetime']['AGO'] as language entry. - */ - function lang() - { - $args = func_get_args(); - $key = $args[0]; - - if (is_array($key)) - { - $lang = &$this->lang[array_shift($key)]; - - foreach ($key as $_key) - { - $lang = &$lang[$_key]; - } - } - else - { - $lang = &$this->lang[$key]; - } - - // Return if language string does not exist - if (!isset($lang) || (!is_string($lang) && !is_array($lang))) - { - return $key; - } - - // If the language entry is a string, we simply mimic sprintf() behaviour - if (is_string($lang)) - { - if (sizeof($args) == 1) - { - return $lang; - } - - // Replace key with language entry and simply pass along... - $args[0] = $lang; - return call_user_func_array('sprintf', $args); - } - else if (sizeof($lang) == 0) - { - // If the language entry is an empty array, we just return the language key - return $args[0]; - } - - // It is an array... now handle different nullar/singular/plural forms - $key_found = false; - - // We now get the first number passed and will select the key based upon this number - for ($i = 1, $num_args = sizeof($args); $i < $num_args; $i++) - { - if (is_int($args[$i]) || is_float($args[$i])) - { - if ($args[$i] == 0 && isset($lang[0])) - { - // We allow each translation using plural forms to specify a version for the case of 0 things, - // so that "0 users" may be displayed as "No users". - $key_found = 0; - break; - } - else - { - $use_plural_form = $this->get_plural_form($args[$i]); - if (isset($lang[$use_plural_form])) - { - // The key we should use exists, so we use it. - $key_found = $use_plural_form; - } - else - { - // If the key we need to use does not exist, we fall back to the previous one. - $numbers = array_keys($lang); - - foreach ($numbers as $num) - { - if ($num > $use_plural_form) - { - break; - } - - $key_found = $num; - } - } - break; - } - } - } - - // Ok, let's check if the key was found, else use the last entry (because it is mostly the plural form) - if ($key_found === false) - { - $numbers = array_keys($lang); - $key_found = end($numbers); - } - - // Use the language string we determined and pass it to sprintf() - $args[0] = $lang[$key_found]; - return call_user_func_array('sprintf', $args); - } - - /** - * Determine which plural form we should use. - * For some languages this is not as simple as for English. - * - * @param $number int|float The number we want to get the plural case for. Float numbers are floored. - * @param $force_rule mixed False to use the plural rule of the language package - * or an integer to force a certain plural rule - * @return int The plural-case we need to use for the number plural-rule combination - */ - function get_plural_form($number, $force_rule = false) - { - $number = (int) $number; - - // Default to English system - $plural_rule = ($force_rule !== false) ? $force_rule : ((isset($this->lang['PLURAL_RULE'])) ? $this->lang['PLURAL_RULE'] : 1); - - return phpbb_get_plural_form($plural_rule, $number); - } - - /** - * Add Language Items - use_db and use_help are assigned where needed (only use them to force inclusion) - * - * @param mixed $lang_set specifies the language entries to include - * @param bool $use_db internal variable for recursion, do not use - * @param bool $use_help internal variable for recursion, do not use - * @param string $ext_name The extension to load language from, or empty for core files - * - * Examples: - * - * $lang_set = array('posting', 'help' => 'faq'); - * $lang_set = array('posting', 'viewtopic', 'help' => array('bbcode', 'faq')) - * $lang_set = array(array('posting', 'viewtopic'), 'help' => array('bbcode', 'faq')) - * $lang_set = 'posting' - * $lang_set = array('help' => 'faq', 'db' => array('help:faq', 'posting')) - * - */ - function add_lang($lang_set, $use_db = false, $use_help = false, $ext_name = '') - { - global $phpEx; - - if (is_array($lang_set)) - { - foreach ($lang_set as $key => $lang_file) - { - // Please do not delete this line. - // We have to force the type here, else [array] language inclusion will not work - $key = (string) $key; - - if ($key == 'db') - { - $this->add_lang($lang_file, true, $use_help, $ext_name); - } - else if ($key == 'help') - { - $this->add_lang($lang_file, $use_db, true, $ext_name); - } - else if (!is_array($lang_file)) - { - $this->set_lang($this->lang, $this->help, $lang_file, $use_db, $use_help, $ext_name); - } - else - { - $this->add_lang($lang_file, $use_db, $use_help, $ext_name); - } - } - unset($lang_set); - } - else if ($lang_set) - { - $this->set_lang($this->lang, $this->help, $lang_set, $use_db, $use_help, $ext_name); - } - } - - /** - * Add Language Items from an extension - use_db and use_help are assigned where needed (only use them to force inclusion) - * - * @param string $ext_name The extension to load language from, or empty for core files - * @param mixed $lang_set specifies the language entries to include - * @param bool $use_db internal variable for recursion, do not use - * @param bool $use_help internal variable for recursion, do not use - */ - function add_lang_ext($ext_name, $lang_set, $use_db = false, $use_help = false) - { - if ($ext_name === '/') - { - $ext_name = ''; - } - - $this->add_lang($lang_set, $use_db, $use_help, $ext_name); - } - - /** - * Set language entry (called by add_lang) - * @access private - */ - function set_lang(&$lang, &$help, $lang_file, $use_db = false, $use_help = false, $ext_name = '') - { - global $phpbb_root_path, $phpEx; - - // Make sure the language name is set (if the user setup did not happen it is not set) - if (!$this->lang_name) - { - global $config; - $this->lang_name = basename($config['default_lang']); - } - - // $lang == $this->lang - // $help == $this->help - // - add appropriate variables here, name them as they are used within the language file... - if (!$use_db) - { - if ($use_help && strpos($lang_file, '/') !== false) - { - $filename = dirname($lang_file) . '/help_' . basename($lang_file); - } - else - { - $filename = (($use_help) ? 'help_' : '') . $lang_file; - } - - if ($ext_name) - { - global $phpbb_extension_manager; - $ext_path = $phpbb_extension_manager->get_extension_path($ext_name, true); - - $lang_path = $ext_path . 'language/'; - } - else - { - $lang_path = $this->lang_path; - } - - if (strpos($phpbb_root_path . $filename, $lang_path . $this->lang_name . '/') === 0) - { - $language_filename = $phpbb_root_path . $filename; - } - else - { - $language_filename = $lang_path . $this->lang_name . '/' . $filename . '.' . $phpEx; - } - - if (!file_exists($language_filename)) - { - global $config; - - if ($this->lang_name == 'en') - { - // The user's selected language is missing the file, the board default's language is missing the file, and the file doesn't exist in /en. - $language_filename = str_replace($lang_path . 'en', $lang_path . $this->data['user_lang'], $language_filename); - trigger_error('Language file ' . $language_filename . ' couldn\'t be opened.', E_USER_ERROR); - } - else if ($this->lang_name == basename($config['default_lang'])) - { - // Fall back to the English Language - $this->lang_name = 'en'; - $this->set_lang($lang, $help, $lang_file, $use_db, $use_help, $ext_name); - } - else if ($this->lang_name == $this->data['user_lang']) - { - // Fall back to the board default language - $this->lang_name = basename($config['default_lang']); - $this->set_lang($lang, $help, $lang_file, $use_db, $use_help, $ext_name); - } - - // Reset the lang name - $this->lang_name = (file_exists($lang_path . $this->data['user_lang'] . "/common.$phpEx")) ? $this->data['user_lang'] : basename($config['default_lang']); - return; - } - - // Do not suppress error if in DEBUG mode - $include_result = (defined('DEBUG')) ? (include $language_filename) : (@include $language_filename); - - if ($include_result === false) - { - trigger_error('Language file ' . $language_filename . ' couldn\'t be opened.', E_USER_ERROR); - } - } - else if ($use_db) - { - // Get Database Language Strings - // Put them into $lang if nothing is prefixed, put them into $help if help: is prefixed - // For example: help:faq, posting - } - } - - /** - * Format user date - * - * @param int $gmepoch unix timestamp - * @param string $format date format in date() notation. | used to indicate relative dates, for example |d m Y|, h:i is translated to Today, h:i. - * @param bool $forcedate force non-relative date format. - * - * @return mixed translated date - */ - function format_date($gmepoch, $format = false, $forcedate = false) - { - static $utc; - - if (!isset($utc)) - { - $utc = new DateTimeZone('UTC'); - } - - $time = new phpbb_datetime($this, "@$gmepoch", $utc); - $time->setTimezone($this->timezone); - - return $time->format($format, $forcedate); - } - - /** - * Create a phpbb_datetime object in the context of the current user - * - * @since 3.1 - * @param string $time String in a format accepted by strtotime(). - * @param DateTimeZone $timezone Time zone of the time. - * @return phpbb_datetime Date time object linked to the current users locale - */ - public function create_datetime($time = 'now', DateTimeZone $timezone = null) - { - $timezone = $timezone ?: $this->timezone; - return new phpbb_datetime($this, $time, $timezone); - } - - /** - * Get the UNIX timestamp for a datetime in the users timezone, so we can store it in the database. - * - * @param string $format Format of the entered date/time - * @param string $time Date/time with the timezone applied - * @param DateTimeZone $timezone Timezone of the date/time, falls back to timezone of current user - * @return int Returns the unix timestamp - */ - public function get_timestamp_from_format($format, $time, DateTimeZone $timezone = null) - { - $timezone = $timezone ?: $this->timezone; - $date = DateTime::createFromFormat($format, $time, $timezone); - return ($date !== false) ? $date->format('U') : false; - } - - /** - * Get language id currently used by the user - */ - function get_iso_lang_id() - { - global $config, $db; - - if (!empty($this->lang_id)) - { - return $this->lang_id; - } - - if (!$this->lang_name) - { - $this->lang_name = $config['default_lang']; - } - - $sql = 'SELECT lang_id - FROM ' . LANG_TABLE . " - WHERE lang_iso = '" . $db->sql_escape($this->lang_name) . "'"; - $result = $db->sql_query($sql); - $this->lang_id = (int) $db->sql_fetchfield('lang_id'); - $db->sql_freeresult($result); - - return $this->lang_id; - } - - /** - * Get users profile fields - */ - function get_profile_fields($user_id) - { - global $db; - - if (isset($this->profile_fields)) - { - return; - } - - $sql = 'SELECT * - FROM ' . PROFILE_FIELDS_DATA_TABLE . " - WHERE user_id = $user_id"; - $result = $db->sql_query_limit($sql, 1); - $this->profile_fields = (!($row = $db->sql_fetchrow($result))) ? array() : $row; - $db->sql_freeresult($result); - } - - /** - * Specify/Get image - */ - function img($img, $alt = '') - { - $alt = (!empty($this->lang[$alt])) ? $this->lang[$alt] : $alt; - return '' . $alt . ''; - } - - /** - * Get option bit field from user options. - * - * @param int $key option key, as defined in $keyoptions property. - * @param int $data bit field value to use, or false to use $this->data['user_options'] - * @return bool true if the option is set in the bit field, false otherwise - */ - function optionget($key, $data = false) - { - $var = ($data !== false) ? $data : $this->data['user_options']; - return phpbb_optionget($this->keyoptions[$key], $var); - } - - /** - * Set option bit field for user options. - * - * @param int $key Option key, as defined in $keyoptions property. - * @param bool $value True to set the option, false to clear the option. - * @param int $data Current bit field value, or false to use $this->data['user_options'] - * @return int|bool If $data is false, the bit field is modified and - * written back to $this->data['user_options'], and - * return value is true if the bit field changed and - * false otherwise. If $data is not false, the new - * bitfield value is returned. - */ - function optionset($key, $value, $data = false) - { - $var = ($data !== false) ? $data : $this->data['user_options']; - - $new_var = phpbb_optionset($this->keyoptions[$key], $value, $var); - - if ($data === false) - { - if ($new_var != $var) - { - $this->data['user_options'] = $new_var; - return true; - } - else - { - return false; - } - } - else - { - return $new_var; - } - } - - /** - * Funtion to make the user leave the NEWLY_REGISTERED system group. - * @access public - */ - function leave_newly_registered() - { - global $db; - - if (empty($this->data['user_new'])) - { - return false; - } - - if (!function_exists('remove_newly_registered')) - { - global $phpbb_root_path, $phpEx; - - include($phpbb_root_path . 'includes/functions_user.' . $phpEx); - } - if ($group = remove_newly_registered($this->data['user_id'], $this->data)) - { - $this->data['group_id'] = $group; - - } - $this->data['user_permissions'] = ''; - $this->data['user_new'] = 0; - - return true; - } - - /** - * Returns all password protected forum ids the user is currently NOT authenticated for. - * - * @return array Array of forum ids - * @access public - */ - function get_passworded_forums() - { - global $db; - - $sql = 'SELECT f.forum_id, fa.user_id - FROM ' . FORUMS_TABLE . ' f - LEFT JOIN ' . FORUMS_ACCESS_TABLE . " fa - ON (fa.forum_id = f.forum_id - AND fa.session_id = '" . $db->sql_escape($this->session_id) . "') - WHERE f.forum_password <> ''"; - $result = $db->sql_query($sql); - - $forum_ids = array(); - while ($row = $db->sql_fetchrow($result)) - { - $forum_id = (int) $row['forum_id']; - - if ($row['user_id'] != $this->data['user_id']) - { - $forum_ids[$forum_id] = $forum_id; - } - } - $db->sql_freeresult($result); - - return $forum_ids; - } -} diff --git a/phpBB/includes/user_loader.php b/phpBB/includes/user_loader.php deleted file mode 100644 index 37bf9648c1..0000000000 --- a/phpBB/includes/user_loader.php +++ /dev/null @@ -1,231 +0,0 @@ -db = $db; - - $this->phpbb_root_path = $phpbb_root_path; - $this->php_ext = $php_ext; - - $this->users_table = $users_table; - } - - /** - * Load user helper - * - * @param array $user_ids - */ - public function load_users(array $user_ids) - { - $user_ids[] = ANONYMOUS; - - // Make user_ids unique and convert to integer. - $user_ids = array_map('intval', array_unique($user_ids)); - - // Do not load users we already have in $this->users - $user_ids = array_diff($user_ids, array_keys($this->users)); - - if (sizeof($user_ids)) - { - $sql = 'SELECT * - FROM ' . $this->users_table . ' - WHERE ' . $this->db->sql_in_set('user_id', $user_ids); - $result = $this->db->sql_query($sql); - - while ($row = $this->db->sql_fetchrow($result)) - { - $this->users[$row['user_id']] = $row; - } - $this->db->sql_freeresult($result); - } - } - - /** - * Load a user by username - * - * Stores the full data in the user cache so they do not need to be loaded again - * Returns the user id so you may use get_user() from the returned value - * - * @param string $username Raw username to load (will be cleaned) - * @return int User ID for the username - */ - public function load_user_by_username($username) - { - $sql = 'SELECT * - FROM ' . $this->users_table . " - WHERE username_clean = '" . $this->db->sql_escape(utf8_clean_string($username)) . "'"; - $result = $this->db->sql_query($sql); - $row = $this->db->sql_fetchrow($result); - $this->db->sql_freeresult($result); - - if ($row) - { - $this->users[$row['user_id']] = $row; - - return $row['user_id']; - } - - return ANONYMOUS; - } - - /** - * Get a user row from our users cache - * - * @param int $user_id User ID of the user you want to retreive - * @param bool $query Should we query the database if this user has not yet been loaded? - * Typically this should be left as false and you should make sure - * you load users ahead of time with load_users() - * @return array|bool Row from the database of the user or Anonymous if the user wasn't loaded/does not exist - * or bool False if the anonymous user was not loaded - */ - public function get_user($user_id, $query = false) - { - if (isset($this->users[$user_id])) - { - return $this->users[$user_id]; - } - // Query them if we must (if ANONYMOUS is sent as the user_id and we have not loaded Anonymous yet, we must load Anonymous as a last resort) - else if ($query || $user_id == ANONYMOUS) - { - $this->load_users(array($user_id)); - - return $this->get_user($user_id); - } - - return $this->get_user(ANONYMOUS); - } - - /** - * Get username - * - * @param int $user_id User ID of the user you want to retreive the username for - * @param string $mode The mode to load (same as get_username_string). One of the following: - * profile (for getting an url to the profile) - * username (for obtaining the username) - * colour (for obtaining the user colour) - * full (for obtaining a html string representing a coloured link to the users profile) - * no_profile (the same as full but forcing no profile link) - * @param string $guest_username Optional parameter to specify the guest username. It will be used in favor of the GUEST language variable then. - * @param string $custom_profile_url Optional parameter to specify a profile url. The user id get appended to this url as &u={user_id} - * @param bool $query Should we query the database if this user has not yet been loaded? - * Typically this should be left as false and you should make sure - * you load users ahead of time with load_users() - * @return string - */ - public function get_username($user_id, $mode, $guest_username = false, $custom_profile_url = false, $query = false) - { - if (!($user = $this->get_user($user_id, $query))) - { - return ''; - } - - return get_username_string($mode, $user['user_id'], $user['username'], $user['user_colour'], $guest_username, $custom_profile_url); - } - - /** - * Get avatar - * - * @param int $user_id User ID of the user you want to retreive the avatar for - * @param bool $query Should we query the database if this user has not yet been loaded? - * Typically this should be left as false and you should make sure - * you load users ahead of time with load_users() - * @return string - */ - public function get_avatar($user_id, $query = false) - { - if (!($user = $this->get_user($user_id, $query))) - { - return ''; - } - - if (!function_exists('get_user_avatar')) - { - include($this->phpbb_root_path . 'includes/functions_display.' . $this->php_ext); - } - - return get_user_avatar($user['user_avatar'], $user['user_avatar_type'], $user['user_avatar_width'], $user['user_avatar_height']); - } - - /** - * Get rank - * - * @param int $user_id User ID of the user you want to retreive the rank for - * @param bool $query Should we query the database if this user has not yet been loaded? - * Typically this should be left as false and you should make sure - * you load users ahead of time with load_users() - * @return array Array with keys 'rank_title', 'rank_img', and 'rank_img_src' - */ - public function get_rank($user_id, $query = false) - { - if (!($user = $this->get_user($user_id, $query))) - { - return ''; - } - - if (!function_exists('get_user_rank')) - { - include($this->phpbb_root_path . 'includes/functions_display.' . $this->php_ext); - } - - $rank = array( - 'rank_title', - 'rank_img', - 'rank_img_src', - ); - - get_user_rank($user['user_rank'], (($user['user_id'] == ANONYMOUS) ? false : $user['user_posts']), $rank['rank_title'], $rank['rank_img'], $rank['rank_img_src']); - - return $rank; - } -} diff --git a/phpBB/install/database_update.php b/phpBB/install/database_update.php index b20ca1e4ea..cd6fb7931b 100644 --- a/phpBB/install/database_update.php +++ b/phpBB/install/database_update.php @@ -82,7 +82,7 @@ $phpbb_adm_relative_path = (isset($phpbb_adm_relative_path)) ? $phpbb_adm_relati $phpbb_admin_path = (defined('PHPBB_ADMIN_PATH')) ? PHPBB_ADMIN_PATH : $phpbb_root_path . $phpbb_adm_relative_path; // Include files -require($phpbb_root_path . 'includes/class_loader.' . $phpEx); +require($phpbb_root_path . 'phpbb/class_loader.' . $phpEx); require($phpbb_root_path . 'includes/functions.' . $phpEx); require($phpbb_root_path . 'includes/functions_content.' . $phpEx); @@ -95,7 +95,7 @@ require($phpbb_root_path . 'includes/utf/utf_tools.' . $phpEx); set_error_handler(defined('PHPBB_MSG_HANDLER') ? PHPBB_MSG_HANDLER : 'msg_handler'); // Setup class loader first -$phpbb_class_loader = new phpbb_class_loader('phpbb_', "{$phpbb_root_path}includes/", $phpEx); +$phpbb_class_loader = new phpbb_class_loader('phpbb_', "{$phpbb_root_path}phpbb/", $phpEx); $phpbb_class_loader->register(); // Set up container (must be done here because extensions table may not exist) @@ -217,7 +217,7 @@ $phpbb_extension_manager = $phpbb_container->get('ext.manager'); $finder = $phpbb_extension_manager->get_finder(); $migrations = $finder - ->core_path('includes/db/migration/data/') + ->core_path('phpbb/db/migration/data/') ->get_classes(); $migrator->set_migrations($migrations); diff --git a/phpBB/install/index.php b/phpBB/install/index.php index 84a2a023f8..45e5777e36 100644 --- a/phpBB/install/index.php +++ b/phpBB/install/index.php @@ -78,7 +78,7 @@ $phpbb_adm_relative_path = (isset($phpbb_adm_relative_path)) ? $phpbb_adm_relati $phpbb_admin_path = (defined('PHPBB_ADMIN_PATH')) ? PHPBB_ADMIN_PATH : $phpbb_root_path . $phpbb_adm_relative_path; // Include essential scripts -require($phpbb_root_path . 'includes/class_loader.' . $phpEx); +require($phpbb_root_path . 'phpbb/class_loader.' . $phpEx); require($phpbb_root_path . 'includes/functions.' . $phpEx); require($phpbb_root_path . 'includes/functions_container.' . $phpEx); @@ -90,7 +90,7 @@ include($phpbb_root_path . 'includes/utf/utf_tools.' . $phpEx); require($phpbb_root_path . 'includes/functions_install.' . $phpEx); // Setup class loader first -$phpbb_class_loader = new phpbb_class_loader('phpbb_', "{$phpbb_root_path}includes/", $phpEx); +$phpbb_class_loader = new phpbb_class_loader('phpbb_', "{$phpbb_root_path}phpbb/", $phpEx); $phpbb_class_loader->register(); $phpbb_class_loader_ext = new phpbb_class_loader('phpbb_ext_', "{$phpbb_root_path}ext/", $phpEx); $phpbb_class_loader_ext->register(); diff --git a/phpBB/install/install_install.php b/phpBB/install/install_install.php index 3d7b6f7c88..ea23c318e3 100644 --- a/phpBB/install/install_install.php +++ b/phpBB/install/install_install.php @@ -1435,7 +1435,7 @@ class install_install extends module $db->sql_return_on_error(true); include_once($phpbb_root_path . 'includes/constants.' . $phpEx); - include_once($phpbb_root_path . 'includes/search/fulltext_native.' . $phpEx); + include_once($phpbb_root_path . 'phpbb/search/fulltext_native.' . $phpEx); // We need to fill the config to let internal functions correctly work $config = new phpbb_config_db($db, new phpbb_cache_driver_null, CONFIG_TABLE); @@ -1888,7 +1888,7 @@ class install_install extends module /** * Populate migrations for the installation * - * This "installs" all migrations from (root path)/includes/db/migrations/data. + * This "installs" all migrations from (root path)/phpbb/db/migrations/data. * "installs" means it adds all migrations to the migrations table, but does not * perform any of the actions in the migrations. * @@ -1900,7 +1900,7 @@ class install_install extends module $finder = $extension_manager->get_finder(); $migrations = $finder - ->core_path('includes/db/migration/data/') + ->core_path('phpbb/db/migration/data/') ->get_classes(); $migrator->populate_migrations($migrations); } diff --git a/phpBB/phpbb/auth/auth.php b/phpBB/phpbb/auth/auth.php new file mode 100644 index 0000000000..279959974d --- /dev/null +++ b/phpBB/phpbb/auth/auth.php @@ -0,0 +1,1080 @@ +acl = $this->cache = $this->acl_options = array(); + $this->acl_forum_ids = false; + + if (($this->acl_options = $cache->get('_acl_options')) === false) + { + $sql = 'SELECT auth_option_id, auth_option, is_global, is_local + FROM ' . ACL_OPTIONS_TABLE . ' + ORDER BY auth_option_id'; + $result = $db->sql_query($sql); + + $global = $local = 0; + $this->acl_options = array(); + while ($row = $db->sql_fetchrow($result)) + { + if ($row['is_global']) + { + $this->acl_options['global'][$row['auth_option']] = $global++; + } + + if ($row['is_local']) + { + $this->acl_options['local'][$row['auth_option']] = $local++; + } + + $this->acl_options['id'][$row['auth_option']] = (int) $row['auth_option_id']; + $this->acl_options['option'][(int) $row['auth_option_id']] = $row['auth_option']; + } + $db->sql_freeresult($result); + + $cache->put('_acl_options', $this->acl_options); + } + + if (!trim($userdata['user_permissions'])) + { + $this->acl_cache($userdata); + } + + // Fill ACL array + $this->_fill_acl($userdata['user_permissions']); + + // Verify bitstring length with options provided... + $renew = false; + $global_length = sizeof($this->acl_options['global']); + $local_length = sizeof($this->acl_options['local']); + + // Specify comparing length (bitstring is padded to 31 bits) + $global_length = ($global_length % 31) ? ($global_length - ($global_length % 31) + 31) : $global_length; + $local_length = ($local_length % 31) ? ($local_length - ($local_length % 31) + 31) : $local_length; + + // You thought we are finished now? Noooo... now compare them. + foreach ($this->acl as $forum_id => $bitstring) + { + if (($forum_id && strlen($bitstring) != $local_length) || (!$forum_id && strlen($bitstring) != $global_length)) + { + $renew = true; + break; + } + } + + // If a bitstring within the list does not match the options, we have a user with incorrect permissions set and need to renew them + if ($renew) + { + $this->acl_cache($userdata); + $this->_fill_acl($userdata['user_permissions']); + } + + return; + } + + /** + * 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 + */ + function _fill_acl($user_permissions) + { + $seq_cache = array(); + $this->acl = array(); + $user_permissions = explode("\n", $user_permissions); + + foreach ($user_permissions as $f => $seq) + { + if ($seq) + { + $i = 0; + + if (!isset($this->acl[$f])) + { + $this->acl[$f] = ''; + } + + while ($subseq = substr($seq, $i, 6)) + { + if (isset($seq_cache[$subseq])) + { + $converted = $seq_cache[$subseq]; + } + else + { + $converted = $seq_cache[$subseq] = str_pad(base_convert($subseq, 36, 2), 31, 0, STR_PAD_LEFT); + } + + // We put the original bitstring into the acl array + $this->acl[$f] .= $converted; + $i += 6; + } + } + } + } + + /** + * Look up an option + * if the option is prefixed with !, then the result becomes negated + * + * If a forum id is specified the local option will be combined with a global option if one exist. + * If a forum id is not specified, only the global option will be checked. + */ + function acl_get($opt, $f = 0) + { + $negate = false; + + if (strpos($opt, '!') === 0) + { + $negate = true; + $opt = substr($opt, 1); + } + + if (!isset($this->cache[$f][$opt])) + { + // We combine the global/local option with an OR because some options are global and local. + // If the user has the global permission the local one is true too and vice versa + $this->cache[$f][$opt] = false; + + // Is this option a global permission setting? + if (isset($this->acl_options['global'][$opt])) + { + if (isset($this->acl[0])) + { + $this->cache[$f][$opt] = $this->acl[0][$this->acl_options['global'][$opt]]; + } + } + + // Is this option a local permission setting? + // But if we check for a global option only, we won't combine the options... + if ($f != 0 && isset($this->acl_options['local'][$opt])) + { + if (isset($this->acl[$f]) && isset($this->acl[$f][$this->acl_options['local'][$opt]])) + { + $this->cache[$f][$opt] |= $this->acl[$f][$this->acl_options['local'][$opt]]; + } + } + } + + // Founder always has all global options set to true... + return ($negate) ? !$this->cache[$f][$opt] : $this->cache[$f][$opt]; + } + + /** + * Get forums with the specified permission setting + * 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 + */ + function acl_getf($opt, $clean = false) + { + $acl_f = array(); + $negate = false; + + if (strpos($opt, '!') === 0) + { + $negate = true; + $opt = substr($opt, 1); + } + + // If we retrieve a list of forums not having permissions in, we need to get every forum_id + if ($negate) + { + if ($this->acl_forum_ids === false) + { + global $db; + + $sql = 'SELECT forum_id + FROM ' . FORUMS_TABLE; + + if (sizeof($this->acl)) + { + $sql .= ' WHERE ' . $db->sql_in_set('forum_id', array_keys($this->acl), true); + } + $result = $db->sql_query($sql); + + $this->acl_forum_ids = array(); + while ($row = $db->sql_fetchrow($result)) + { + $this->acl_forum_ids[] = $row['forum_id']; + } + $db->sql_freeresult($result); + } + } + + if (isset($this->acl_options['local'][$opt])) + { + foreach ($this->acl as $f => $bitstring) + { + // Skip global settings + if (!$f) + { + continue; + } + + $allowed = (!isset($this->cache[$f][$opt])) ? $this->acl_get($opt, $f) : $this->cache[$f][$opt]; + + if (!$clean) + { + $acl_f[$f][$opt] = ($negate) ? !$allowed : $allowed; + } + else + { + if (($negate && !$allowed) || (!$negate && $allowed)) + { + $acl_f[$f][$opt] = 1; + } + } + } + } + + // If we get forum_ids not having this permission, we need to fill the remaining parts + if ($negate && sizeof($this->acl_forum_ids)) + { + foreach ($this->acl_forum_ids as $f) + { + $acl_f[$f][$opt] = 1; + } + } + + return $acl_f; + } + + /** + * Get local permission state for any forum. + * + * Returns true if user has the permission in one or more forums, false if in no forum. + * If global option is checked it returns the global state (same as acl_get($opt)) + * Local option has precedence... + */ + function acl_getf_global($opt) + { + if (is_array($opt)) + { + // evaluates to true as soon as acl_getf_global is true for one option + foreach ($opt as $check_option) + { + if ($this->acl_getf_global($check_option)) + { + return true; + } + } + + return false; + } + + if (isset($this->acl_options['local'][$opt])) + { + foreach ($this->acl as $f => $bitstring) + { + // Skip global settings + if (!$f) + { + continue; + } + + // as soon as the user has any permission we're done so return true + if ((!isset($this->cache[$f][$opt])) ? $this->acl_get($opt, $f) : $this->cache[$f][$opt]) + { + return true; + } + } + } + else if (isset($this->acl_options['global'][$opt])) + { + return $this->acl_get($opt); + } + + return false; + } + + /** + * Get permission settings (more than one) + */ + function acl_gets() + { + $args = func_get_args(); + $f = array_pop($args); + + if (!is_numeric($f)) + { + $args[] = $f; + $f = 0; + } + + // alternate syntax: acl_gets(array('m_', 'a_'), $forum_id) + if (is_array($args[0])) + { + $args = $args[0]; + } + + $acl = 0; + foreach ($args as $opt) + { + $acl |= $this->acl_get($opt, $f); + } + + return $acl; + } + + /** + * Get permission listing based on user_id/options/forum_ids + * + * Be careful when using this function with permissions a_, m_, u_ and f_ ! + * It may not work correctly. When a user group grants an a_* permission, + * e.g. a_foo, but the user's a_foo permission is set to "Never", then + * the user does not in fact have the a_ permission. + * But the user will still be listed as having the a_ permission. + * + * For more information see: http://tracker.phpbb.com/browse/PHPBB3-10252 + */ + function acl_get_list($user_id = false, $opts = false, $forum_id = false) + { + if ($user_id !== false && !is_array($user_id) && $opts === false && $forum_id === false) + { + $hold_ary = array($user_id => $this->acl_raw_data_single_user($user_id)); + } + else + { + $hold_ary = $this->acl_raw_data($user_id, $opts, $forum_id); + } + + $auth_ary = array(); + foreach ($hold_ary as $user_id => $forum_ary) + { + foreach ($forum_ary as $forum_id => $auth_option_ary) + { + foreach ($auth_option_ary as $auth_option => $auth_setting) + { + if ($auth_setting) + { + $auth_ary[$forum_id][$auth_option][] = $user_id; + } + } + } + } + + return $auth_ary; + } + + /** + * Cache data to user_permissions row + */ + function acl_cache(&$userdata) + { + global $db; + + // Empty user_permissions + $userdata['user_permissions'] = ''; + + $hold_ary = $this->acl_raw_data_single_user($userdata['user_id']); + + // Key 0 in $hold_ary are global options, all others are forum_ids + + // If this user is founder we're going to force fill the admin options ... + if ($userdata['user_type'] == USER_FOUNDER) + { + foreach ($this->acl_options['global'] as $opt => $id) + { + if (strpos($opt, 'a_') === 0) + { + $hold_ary[0][$this->acl_options['id'][$opt]] = ACL_YES; + } + } + } + + $hold_str = $this->build_bitstring($hold_ary); + + if ($hold_str) + { + $userdata['user_permissions'] = $hold_str; + + $sql = 'UPDATE ' . USERS_TABLE . " + SET user_permissions = '" . $db->sql_escape($userdata['user_permissions']) . "', + user_perm_from = 0 + WHERE user_id = " . $userdata['user_id']; + $db->sql_query($sql); + } + + return; + } + + /** + * Build bitstring from permission set + */ + function build_bitstring(&$hold_ary) + { + $hold_str = ''; + + if (sizeof($hold_ary)) + { + ksort($hold_ary); + + $last_f = 0; + + foreach ($hold_ary as $f => $auth_ary) + { + $ary_key = (!$f) ? 'global' : 'local'; + + $bitstring = array(); + foreach ($this->acl_options[$ary_key] as $opt => $id) + { + if (isset($auth_ary[$this->acl_options['id'][$opt]])) + { + $bitstring[$id] = $auth_ary[$this->acl_options['id'][$opt]]; + + $option_key = substr($opt, 0, strpos($opt, '_') + 1); + + // If one option is allowed, the global permission for this option has to be allowed too + // example: if the user has the a_ permission this means he has one or more a_* permissions + if ($auth_ary[$this->acl_options['id'][$opt]] == ACL_YES && (!isset($bitstring[$this->acl_options[$ary_key][$option_key]]) || $bitstring[$this->acl_options[$ary_key][$option_key]] == ACL_NEVER)) + { + $bitstring[$this->acl_options[$ary_key][$option_key]] = ACL_YES; + } + } + else + { + $bitstring[$id] = ACL_NEVER; + } + } + + // Now this bitstring defines the permission setting for the current forum $f (or global setting) + $bitstring = implode('', $bitstring); + + // The line number indicates the id, therefore we have to add empty lines for those ids not present + $hold_str .= str_repeat("\n", $f - $last_f); + + // Convert bitstring for storage - we do not use binary/bytes because PHP's string functions are not fully binary safe + for ($i = 0, $bit_length = strlen($bitstring); $i < $bit_length; $i += 31) + { + $hold_str .= str_pad(base_convert(str_pad(substr($bitstring, $i, 31), 31, 0, STR_PAD_RIGHT), 2, 36), 6, 0, STR_PAD_LEFT); + } + + $last_f = $f; + } + unset($bitstring); + + $hold_str = rtrim($hold_str); + } + + return $hold_str; + } + + /** + * Clear one or all users cached permission settings + */ + function acl_clear_prefetch($user_id = false) + { + global $db, $cache; + + // Rebuild options cache + $cache->destroy('_role_cache'); + + $sql = 'SELECT * + FROM ' . ACL_ROLES_DATA_TABLE . ' + ORDER BY role_id ASC'; + $result = $db->sql_query($sql); + + $this->role_cache = array(); + while ($row = $db->sql_fetchrow($result)) + { + $this->role_cache[$row['role_id']][$row['auth_option_id']] = (int) $row['auth_setting']; + } + $db->sql_freeresult($result); + + foreach ($this->role_cache as $role_id => $role_options) + { + $this->role_cache[$role_id] = serialize($role_options); + } + + $cache->put('_role_cache', $this->role_cache); + + // Now empty user permissions + $where_sql = ''; + + if ($user_id !== false) + { + $user_id = (!is_array($user_id)) ? $user_id = array((int) $user_id) : array_map('intval', $user_id); + $where_sql = ' WHERE ' . $db->sql_in_set('user_id', $user_id); + } + + $sql = 'UPDATE ' . USERS_TABLE . " + SET user_permissions = '', + user_perm_from = 0 + $where_sql"; + $db->sql_query($sql); + + return; + } + + /** + * Get assigned roles + */ + function acl_role_data($user_type, $role_type, $ug_id = false, $forum_id = false) + { + global $db; + + $roles = array(); + + $sql_id = ($user_type == 'user') ? 'user_id' : 'group_id'; + + $sql_ug = ($ug_id !== false) ? ((!is_array($ug_id)) ? "AND a.$sql_id = $ug_id" : 'AND ' . $db->sql_in_set("a.$sql_id", $ug_id)) : ''; + $sql_forum = ($forum_id !== false) ? ((!is_array($forum_id)) ? "AND a.forum_id = $forum_id" : 'AND ' . $db->sql_in_set('a.forum_id', $forum_id)) : ''; + + // Grab assigned roles... + $sql = 'SELECT a.auth_role_id, a.' . $sql_id . ', a.forum_id + FROM ' . (($user_type == 'user') ? ACL_USERS_TABLE : ACL_GROUPS_TABLE) . ' a, ' . ACL_ROLES_TABLE . " r + WHERE a.auth_role_id = r.role_id + AND r.role_type = '" . $db->sql_escape($role_type) . "' + $sql_ug + $sql_forum + ORDER BY r.role_order ASC"; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $roles[$row[$sql_id]][$row['forum_id']] = $row['auth_role_id']; + } + $db->sql_freeresult($result); + + return $roles; + } + + /** + * Get raw acl data based on user/option/forum + */ + function acl_raw_data($user_id = false, $opts = false, $forum_id = false) + { + global $db; + + $sql_user = ($user_id !== false) ? ((!is_array($user_id)) ? 'user_id = ' . (int) $user_id : $db->sql_in_set('user_id', array_map('intval', $user_id))) : ''; + $sql_forum = ($forum_id !== false) ? ((!is_array($forum_id)) ? 'AND a.forum_id = ' . (int) $forum_id : 'AND ' . $db->sql_in_set('a.forum_id', array_map('intval', $forum_id))) : ''; + + $sql_opts = $sql_opts_select = $sql_opts_from = ''; + $hold_ary = array(); + + if ($opts !== false) + { + $sql_opts_select = ', ao.auth_option'; + $sql_opts_from = ', ' . ACL_OPTIONS_TABLE . ' ao'; + $this->build_auth_option_statement('ao.auth_option', $opts, $sql_opts); + } + + $sql_ary = array(); + + // Grab non-role settings - user-specific + $sql_ary[] = 'SELECT a.user_id, a.forum_id, a.auth_setting, a.auth_option_id' . $sql_opts_select . ' + FROM ' . ACL_USERS_TABLE . ' a' . $sql_opts_from . ' + WHERE a.auth_role_id = 0 ' . + (($sql_opts_from) ? 'AND a.auth_option_id = ao.auth_option_id ' : '') . + (($sql_user) ? 'AND a.' . $sql_user : '') . " + $sql_forum + $sql_opts"; + + // Now the role settings - user-specific + $sql_ary[] = 'SELECT a.user_id, a.forum_id, r.auth_option_id, r.auth_setting, r.auth_option_id' . $sql_opts_select . ' + FROM ' . ACL_USERS_TABLE . ' a, ' . ACL_ROLES_DATA_TABLE . ' r' . $sql_opts_from . ' + WHERE a.auth_role_id = r.role_id ' . + (($sql_opts_from) ? 'AND r.auth_option_id = ao.auth_option_id ' : '') . + (($sql_user) ? 'AND a.' . $sql_user : '') . " + $sql_forum + $sql_opts"; + + foreach ($sql_ary as $sql) + { + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $option = ($sql_opts_select) ? $row['auth_option'] : $this->acl_options['option'][$row['auth_option_id']]; + $hold_ary[$row['user_id']][$row['forum_id']][$option] = $row['auth_setting']; + } + $db->sql_freeresult($result); + } + + $sql_ary = array(); + + // Now grab group settings - non-role specific... + $sql_ary[] = 'SELECT ug.user_id, a.forum_id, a.auth_setting, a.auth_option_id' . $sql_opts_select . ' + FROM ' . ACL_GROUPS_TABLE . ' a, ' . USER_GROUP_TABLE . ' ug, ' . GROUPS_TABLE . ' g' . $sql_opts_from . ' + WHERE a.auth_role_id = 0 ' . + (($sql_opts_from) ? 'AND a.auth_option_id = ao.auth_option_id ' : '') . ' + AND a.group_id = ug.group_id + AND g.group_id = ug.group_id + AND ug.user_pending = 0 + AND NOT (ug.group_leader = 1 AND g.group_skip_auth = 1) + ' . (($sql_user) ? 'AND ug.' . $sql_user : '') . " + $sql_forum + $sql_opts"; + + // Now grab group settings - role specific... + $sql_ary[] = 'SELECT ug.user_id, a.forum_id, r.auth_setting, r.auth_option_id' . $sql_opts_select . ' + FROM ' . ACL_GROUPS_TABLE . ' a, ' . USER_GROUP_TABLE . ' ug, ' . GROUPS_TABLE . ' g, ' . ACL_ROLES_DATA_TABLE . ' r' . $sql_opts_from . ' + WHERE a.auth_role_id = r.role_id ' . + (($sql_opts_from) ? 'AND r.auth_option_id = ao.auth_option_id ' : '') . ' + AND a.group_id = ug.group_id + AND g.group_id = ug.group_id + AND ug.user_pending = 0 + AND NOT (ug.group_leader = 1 AND g.group_skip_auth = 1) + ' . (($sql_user) ? 'AND ug.' . $sql_user : '') . " + $sql_forum + $sql_opts"; + + foreach ($sql_ary as $sql) + { + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $option = ($sql_opts_select) ? $row['auth_option'] : $this->acl_options['option'][$row['auth_option_id']]; + + if (!isset($hold_ary[$row['user_id']][$row['forum_id']][$option]) || (isset($hold_ary[$row['user_id']][$row['forum_id']][$option]) && $hold_ary[$row['user_id']][$row['forum_id']][$option] != ACL_NEVER)) + { + $hold_ary[$row['user_id']][$row['forum_id']][$option] = $row['auth_setting']; + + // If we detect ACL_NEVER, we will unset the flag option (within building the bitstring it is correctly set again) + if ($row['auth_setting'] == ACL_NEVER) + { + $flag = substr($option, 0, strpos($option, '_') + 1); + + if (isset($hold_ary[$row['user_id']][$row['forum_id']][$flag]) && $hold_ary[$row['user_id']][$row['forum_id']][$flag] == ACL_YES) + { + unset($hold_ary[$row['user_id']][$row['forum_id']][$flag]); + +/* if (in_array(ACL_YES, $hold_ary[$row['user_id']][$row['forum_id']])) + { + $hold_ary[$row['user_id']][$row['forum_id']][$flag] = ACL_YES; + } +*/ + } + } + } + } + $db->sql_freeresult($result); + } + + return $hold_ary; + } + + /** + * Get raw user based permission settings + */ + function acl_user_raw_data($user_id = false, $opts = false, $forum_id = false) + { + global $db; + + $sql_user = ($user_id !== false) ? ((!is_array($user_id)) ? 'user_id = ' . (int) $user_id : $db->sql_in_set('user_id', array_map('intval', $user_id))) : ''; + $sql_forum = ($forum_id !== false) ? ((!is_array($forum_id)) ? 'AND a.forum_id = ' . (int) $forum_id : 'AND ' . $db->sql_in_set('a.forum_id', array_map('intval', $forum_id))) : ''; + + $sql_opts = ''; + $hold_ary = $sql_ary = array(); + + if ($opts !== false) + { + $this->build_auth_option_statement('ao.auth_option', $opts, $sql_opts); + } + + // Grab user settings - non-role specific... + $sql_ary[] = 'SELECT a.user_id, a.forum_id, a.auth_setting, a.auth_option_id, ao.auth_option + FROM ' . ACL_USERS_TABLE . ' a, ' . ACL_OPTIONS_TABLE . ' ao + WHERE a.auth_role_id = 0 + AND a.auth_option_id = ao.auth_option_id ' . + (($sql_user) ? 'AND a.' . $sql_user : '') . " + $sql_forum + $sql_opts + ORDER BY a.forum_id, ao.auth_option"; + + // Now the role settings - user-specific + $sql_ary[] = 'SELECT a.user_id, a.forum_id, r.auth_option_id, r.auth_setting, r.auth_option_id, ao.auth_option + FROM ' . ACL_USERS_TABLE . ' a, ' . ACL_ROLES_DATA_TABLE . ' r, ' . ACL_OPTIONS_TABLE . ' ao + WHERE a.auth_role_id = r.role_id + AND r.auth_option_id = ao.auth_option_id ' . + (($sql_user) ? 'AND a.' . $sql_user : '') . " + $sql_forum + $sql_opts + ORDER BY a.forum_id, ao.auth_option"; + + foreach ($sql_ary as $sql) + { + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $hold_ary[$row['user_id']][$row['forum_id']][$row['auth_option']] = $row['auth_setting']; + } + $db->sql_freeresult($result); + } + + return $hold_ary; + } + + /** + * Get raw group based permission settings + */ + function acl_group_raw_data($group_id = false, $opts = false, $forum_id = false) + { + global $db; + + $sql_group = ($group_id !== false) ? ((!is_array($group_id)) ? 'group_id = ' . (int) $group_id : $db->sql_in_set('group_id', array_map('intval', $group_id))) : ''; + $sql_forum = ($forum_id !== false) ? ((!is_array($forum_id)) ? 'AND a.forum_id = ' . (int) $forum_id : 'AND ' . $db->sql_in_set('a.forum_id', array_map('intval', $forum_id))) : ''; + + $sql_opts = ''; + $hold_ary = $sql_ary = array(); + + if ($opts !== false) + { + $this->build_auth_option_statement('ao.auth_option', $opts, $sql_opts); + } + + // Grab group settings - non-role specific... + $sql_ary[] = 'SELECT a.group_id, a.forum_id, a.auth_setting, a.auth_option_id, ao.auth_option + FROM ' . ACL_GROUPS_TABLE . ' a, ' . ACL_OPTIONS_TABLE . ' ao + WHERE a.auth_role_id = 0 + AND a.auth_option_id = ao.auth_option_id ' . + (($sql_group) ? 'AND a.' . $sql_group : '') . " + $sql_forum + $sql_opts + ORDER BY a.forum_id, ao.auth_option"; + + // Now grab group settings - role specific... + $sql_ary[] = 'SELECT a.group_id, a.forum_id, r.auth_setting, r.auth_option_id, ao.auth_option + FROM ' . ACL_GROUPS_TABLE . ' a, ' . ACL_ROLES_DATA_TABLE . ' r, ' . ACL_OPTIONS_TABLE . ' ao + WHERE a.auth_role_id = r.role_id + AND r.auth_option_id = ao.auth_option_id ' . + (($sql_group) ? 'AND a.' . $sql_group : '') . " + $sql_forum + $sql_opts + ORDER BY a.forum_id, ao.auth_option"; + + foreach ($sql_ary as $sql) + { + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $hold_ary[$row['group_id']][$row['forum_id']][$row['auth_option']] = $row['auth_setting']; + } + $db->sql_freeresult($result); + } + + return $hold_ary; + } + + /** + * Get raw acl data based on user for caching user_permissions + * This function returns the same data as acl_raw_data(), but without the user id as the first key within the array. + */ + function acl_raw_data_single_user($user_id) + { + global $db, $cache; + + // Check if the role-cache is there + if (($this->role_cache = $cache->get('_role_cache')) === false) + { + $this->role_cache = array(); + + // We pre-fetch roles + $sql = 'SELECT * + FROM ' . ACL_ROLES_DATA_TABLE . ' + ORDER BY role_id ASC'; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $this->role_cache[$row['role_id']][$row['auth_option_id']] = (int) $row['auth_setting']; + } + $db->sql_freeresult($result); + + foreach ($this->role_cache as $role_id => $role_options) + { + $this->role_cache[$role_id] = serialize($role_options); + } + + $cache->put('_role_cache', $this->role_cache); + } + + $hold_ary = array(); + + // Grab user-specific permission settings + $sql = 'SELECT forum_id, auth_option_id, auth_role_id, auth_setting + FROM ' . ACL_USERS_TABLE . ' + WHERE user_id = ' . $user_id; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + // If a role is assigned, assign all options included within this role. Else, only set this one option. + if ($row['auth_role_id']) + { + $hold_ary[$row['forum_id']] = (empty($hold_ary[$row['forum_id']])) ? unserialize($this->role_cache[$row['auth_role_id']]) : $hold_ary[$row['forum_id']] + unserialize($this->role_cache[$row['auth_role_id']]); + } + else + { + $hold_ary[$row['forum_id']][$row['auth_option_id']] = $row['auth_setting']; + } + } + $db->sql_freeresult($result); + + // Now grab group-specific permission settings + $sql = 'SELECT a.forum_id, a.auth_option_id, a.auth_role_id, a.auth_setting + FROM ' . ACL_GROUPS_TABLE . ' a, ' . USER_GROUP_TABLE . ' ug, ' . GROUPS_TABLE . ' g + WHERE a.group_id = ug.group_id + AND g.group_id = ug.group_id + AND ug.user_pending = 0 + AND NOT (ug.group_leader = 1 AND g.group_skip_auth = 1) + AND ug.user_id = ' . $user_id; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + if (!$row['auth_role_id']) + { + $this->_set_group_hold_ary($hold_ary[$row['forum_id']], $row['auth_option_id'], $row['auth_setting']); + } + else if (!empty($this->role_cache[$row['auth_role_id']])) + { + foreach (unserialize($this->role_cache[$row['auth_role_id']]) as $option_id => $setting) + { + $this->_set_group_hold_ary($hold_ary[$row['forum_id']], $option_id, $setting); + } + } + } + $db->sql_freeresult($result); + + return $hold_ary; + } + + /** + * Private function snippet for setting a specific piece of the hold_ary + */ + function _set_group_hold_ary(&$hold_ary, $option_id, $setting) + { + if (!isset($hold_ary[$option_id]) || (isset($hold_ary[$option_id]) && $hold_ary[$option_id] != ACL_NEVER)) + { + $hold_ary[$option_id] = $setting; + + // If we detect ACL_NEVER, we will unset the flag option (within building the bitstring it is correctly set again) + if ($setting == ACL_NEVER) + { + $flag = substr($this->acl_options['option'][$option_id], 0, strpos($this->acl_options['option'][$option_id], '_') + 1); + $flag = (int) $this->acl_options['id'][$flag]; + + if (isset($hold_ary[$flag]) && $hold_ary[$flag] == ACL_YES) + { + unset($hold_ary[$flag]); + +/* This is uncommented, because i suspect this being slightly wrong due to mixed permission classes being possible + if (in_array(ACL_YES, $hold_ary)) + { + $hold_ary[$flag] = ACL_YES; + }*/ + } + } + } + } + + /** + * Authentication plug-ins is largely down to Sergey Kanareykin, our thanks to him. + */ + function login($username, $password, $autologin = false, $viewonline = 1, $admin = 0) + { + global $config, $db, $user, $phpbb_root_path, $phpEx, $phpbb_container; + + $method = trim(basename($config['auth_method'])); + + $provider = $phpbb_container->get('auth.provider.' . $method); + if ($provider) + { + $login = $provider->login($username, $password); + + // If the auth module wants us to create an empty profile do so and then treat the status as LOGIN_SUCCESS + if ($login['status'] == LOGIN_SUCCESS_CREATE_PROFILE) + { + // we are going to use the user_add function so include functions_user.php if it wasn't defined yet + if (!function_exists('user_add')) + { + include($phpbb_root_path . 'includes/functions_user.' . $phpEx); + } + + user_add($login['user_row'], (isset($login['cp_data'])) ? $login['cp_data'] : false); + + $sql = 'SELECT user_id, username, user_password, user_passchg, user_email, user_type + FROM ' . USERS_TABLE . " + WHERE username_clean = '" . $db->sql_escape(utf8_clean_string($username)) . "'"; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$row) + { + return array( + 'status' => LOGIN_ERROR_EXTERNAL_AUTH, + 'error_msg' => 'AUTH_NO_PROFILE_CREATED', + 'user_row' => array('user_id' => ANONYMOUS), + ); + } + + $login = array( + 'status' => LOGIN_SUCCESS, + 'error_msg' => false, + 'user_row' => $row, + ); + } + + // If login succeeded, we will log the user in... else we pass the login array through... + if ($login['status'] == LOGIN_SUCCESS) + { + $old_session_id = $user->session_id; + + if ($admin) + { + global $SID, $_SID; + + $cookie_expire = time() - 31536000; + $user->set_cookie('u', '', $cookie_expire); + $user->set_cookie('sid', '', $cookie_expire); + unset($cookie_expire); + + $SID = '?sid='; + $user->session_id = $_SID = ''; + } + + $result = $user->session_create($login['user_row']['user_id'], $admin, $autologin, $viewonline); + + // Successful session creation + if ($result === true) + { + // If admin re-authentication we remove the old session entry because a new one has been created... + if ($admin) + { + // the login array is used because the user ids do not differ for re-authentication + $sql = 'DELETE FROM ' . SESSIONS_TABLE . " + WHERE session_id = '" . $db->sql_escape($old_session_id) . "' + AND session_user_id = {$login['user_row']['user_id']}"; + $db->sql_query($sql); + } + + return array( + 'status' => LOGIN_SUCCESS, + 'error_msg' => false, + 'user_row' => $login['user_row'], + ); + } + + return array( + 'status' => LOGIN_BREAK, + 'error_msg' => $result, + 'user_row' => $login['user_row'], + ); + } + + return $login; + } + + trigger_error('Authentication method not found', E_USER_ERROR); + } + + /** + * Fill auth_option statement for later querying based on the supplied options + */ + function build_auth_option_statement($key, $auth_options, &$sql_opts) + { + global $db; + + if (!is_array($auth_options)) + { + if (strpos($auth_options, '%') !== false) + { + $sql_opts = "AND $key " . $db->sql_like_expression(str_replace('%', $db->any_char, $auth_options)); + } + else + { + $sql_opts = "AND $key = '" . $db->sql_escape($auth_options) . "'"; + } + } + else + { + $is_like_expression = false; + + foreach ($auth_options as $option) + { + if (strpos($option, '%') !== false) + { + $is_like_expression = true; + } + } + + if (!$is_like_expression) + { + $sql_opts = 'AND ' . $db->sql_in_set($key, $auth_options); + } + else + { + $sql = array(); + + foreach ($auth_options as $option) + { + if (strpos($option, '%') !== false) + { + $sql[] = $key . ' ' . $db->sql_like_expression(str_replace('%', $db->any_char, $option)); + } + else + { + $sql[] = $key . " = '" . $db->sql_escape($option) . "'"; + } + } + + $sql_opts = 'AND (' . implode(' OR ', $sql) . ')'; + } + } + } +} diff --git a/phpBB/phpbb/auth/index.htm b/phpBB/phpbb/auth/index.htm new file mode 100644 index 0000000000..ee1f723a7d --- /dev/null +++ b/phpBB/phpbb/auth/index.htm @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/phpBB/phpbb/auth/provider/apache.php b/phpBB/phpbb/auth/provider/apache.php new file mode 100644 index 0000000000..2e80436f78 --- /dev/null +++ b/phpBB/phpbb/auth/provider/apache.php @@ -0,0 +1,259 @@ +db = $db; + $this->config = $config; + $this->request = $request; + $this->user = $user; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + } + + /** + * {@inheritdoc} + */ + public function init() + { + if (!$this->request->is_set('PHP_AUTH_USER', phpbb_request_interface::SERVER) || $this->user->data['username'] !== htmlspecialchars_decode($this->request->server('PHP_AUTH_USER'))) + { + return $this->user->lang['APACHE_SETUP_BEFORE_USE']; + } + return false; + } + + /** + * {@inheritdoc} + */ + public function login($username, $password) + { + // do not allow empty password + if (!$password) + { + return array( + 'status' => LOGIN_ERROR_PASSWORD, + 'error_msg' => 'NO_PASSWORD_SUPPLIED', + 'user_row' => array('user_id' => ANONYMOUS), + ); + } + + if (!$username) + { + return array( + 'status' => LOGIN_ERROR_USERNAME, + 'error_msg' => 'LOGIN_ERROR_USERNAME', + 'user_row' => array('user_id' => ANONYMOUS), + ); + } + + if (!$this->request->is_set('PHP_AUTH_USER', phpbb_request_interface::SERVER)) + { + return array( + 'status' => LOGIN_ERROR_EXTERNAL_AUTH, + 'error_msg' => 'LOGIN_ERROR_EXTERNAL_AUTH_APACHE', + 'user_row' => array('user_id' => ANONYMOUS), + ); + } + + $php_auth_user = htmlspecialchars_decode($this->request->server('PHP_AUTH_USER')); + $php_auth_pw = htmlspecialchars_decode($this->request->server('PHP_AUTH_PW')); + + if (!empty($php_auth_user) && !empty($php_auth_pw)) + { + if ($php_auth_user !== $username) + { + return array( + 'status' => LOGIN_ERROR_USERNAME, + 'error_msg' => 'LOGIN_ERROR_USERNAME', + 'user_row' => array('user_id' => ANONYMOUS), + ); + } + + $sql = 'SELECT user_id, username, user_password, user_passchg, user_email, user_type + FROM ' . USERS_TABLE . " + WHERE username = '" . $this->db->sql_escape($php_auth_user) . "'"; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if ($row) + { + // User inactive... + if ($row['user_type'] == USER_INACTIVE || $row['user_type'] == USER_IGNORE) + { + return array( + 'status' => LOGIN_ERROR_ACTIVE, + 'error_msg' => 'ACTIVE_ERROR', + 'user_row' => $row, + ); + } + + // Successful login... + return array( + 'status' => LOGIN_SUCCESS, + 'error_msg' => false, + 'user_row' => $row, + ); + } + + // this is the user's first login so create an empty profile + return array( + 'status' => LOGIN_SUCCESS_CREATE_PROFILE, + 'error_msg' => false, + 'user_row' => user_row_apache($php_auth_user, $php_auth_pw), + ); + } + + // Not logged into apache + return array( + 'status' => LOGIN_ERROR_EXTERNAL_AUTH, + 'error_msg' => 'LOGIN_ERROR_EXTERNAL_AUTH_APACHE', + 'user_row' => array('user_id' => ANONYMOUS), + ); + } + + /** + * {@inheritdoc} + */ + public function autologin() + { + if (!$this->request->is_set('PHP_AUTH_USER', phpbb_request_interface::SERVER)) + { + return array(); + } + + $php_auth_user = htmlspecialchars_decode($this->request->server('PHP_AUTH_USER')); + $php_auth_pw = htmlspecialchars_decode($this->request->server('PHP_AUTH_PW')); + + if (!empty($php_auth_user) && !empty($php_auth_pw)) + { + set_var($php_auth_user, $php_auth_user, 'string', true); + set_var($php_auth_pw, $php_auth_pw, 'string', true); + + $sql = 'SELECT * + FROM ' . USERS_TABLE . " + WHERE username = '" . $this->db->sql_escape($php_auth_user) . "'"; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if ($row) + { + return ($row['user_type'] == USER_INACTIVE || $row['user_type'] == USER_IGNORE) ? array() : $row; + } + + if (!function_exists('user_add')) + { + include($this->phpbb_root_path . 'includes/functions_user.' . $this->php_ext); + } + + // create the user if he does not exist yet + user_add(user_row_apache($php_auth_user, $php_auth_pw)); + + $sql = 'SELECT * + FROM ' . USERS_TABLE . " + WHERE username_clean = '" . $this->db->sql_escape(utf8_clean_string($php_auth_user)) . "'"; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if ($row) + { + return $row; + } + } + + return array(); + } + + /** + * This function generates an array which can be passed to the user_add + * function in order to create a user + * + * @param string $username The username of the new user. + * @param string $password The password of the new user. + * @return array Contains data that can be passed directly to + * the user_add function. + */ + private function user_row($username, $password) + { + // first retrieve default group id + $sql = 'SELECT group_id + FROM ' . GROUPS_TABLE . " + WHERE group_name = '" . $this->db->sql_escape('REGISTERED') . "' + AND group_type = " . GROUP_SPECIAL; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if (!$row) + { + trigger_error('NO_GROUP'); + } + + // generate user account data + return array( + 'username' => $username, + 'user_password' => phpbb_hash($password), + 'user_email' => '', + 'group_id' => (int) $row['group_id'], + 'user_type' => USER_NORMAL, + 'user_ip' => $this->user->ip, + 'user_new' => ($this->config['new_member_post_limit']) ? 1 : 0, + ); + } + + /** + * {@inheritdoc} + */ + public function validate_session($user) + { + // Check if PHP_AUTH_USER is set and handle this case + if ($this->request->is_set('PHP_AUTH_USER', phpbb_request_interface::SERVER)) + { + $php_auth_user = $this->request->server('PHP_AUTH_USER'); + + return ($php_auth_user === $user['username']) ? true : false; + } + + // PHP_AUTH_USER is not set. A valid session is now determined by the user type (anonymous/bot or not) + if ($user['user_type'] == USER_IGNORE) + { + return true; + } + + return false; + } +} diff --git a/phpBB/phpbb/auth/provider/base.php b/phpBB/phpbb/auth/provider/base.php new file mode 100644 index 0000000000..7eaf8bb2d3 --- /dev/null +++ b/phpBB/phpbb/auth/provider/base.php @@ -0,0 +1,72 @@ +db = $db; + $this->config = $config; + $this->request = $request; + $this->user = $user; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + } + + /** + * {@inheritdoc} + */ + public function login($username, $password) + { + // Auth plugins get the password untrimmed. + // For compatibility we trim() here. + $password = trim($password); + + // do not allow empty password + if (!$password) + { + return array( + 'status' => LOGIN_ERROR_PASSWORD, + 'error_msg' => 'NO_PASSWORD_SUPPLIED', + 'user_row' => array('user_id' => ANONYMOUS), + ); + } + + if (!$username) + { + return array( + 'status' => LOGIN_ERROR_USERNAME, + 'error_msg' => 'LOGIN_ERROR_USERNAME', + 'user_row' => array('user_id' => ANONYMOUS), + ); + } + + $username_clean = utf8_clean_string($username); + + $sql = 'SELECT user_id, username, user_password, user_passchg, user_pass_convert, user_email, user_type, user_login_attempts + FROM ' . USERS_TABLE . " + WHERE username_clean = '" . $this->db->sql_escape($username_clean) . "'"; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if (($this->user->ip && !$this->config['ip_login_limit_use_forwarded']) || + ($this->user->forwarded_for && $this->config['ip_login_limit_use_forwarded'])) + { + $sql = 'SELECT COUNT(*) AS attempts + FROM ' . LOGIN_ATTEMPT_TABLE . ' + WHERE attempt_time > ' . (time() - (int) $this->config['ip_login_limit_time']); + if ($this->config['ip_login_limit_use_forwarded']) + { + $sql .= " AND attempt_forwarded_for = '" . $this->db->sql_escape($this->user->forwarded_for) . "'"; + } + else + { + $sql .= " AND attempt_ip = '" . $this->db->sql_escape($this->user->ip) . "' "; + } + + $result = $this->db->sql_query($sql); + $attempts = (int) $this->db->sql_fetchfield('attempts'); + $this->db->sql_freeresult($result); + + $attempt_data = array( + 'attempt_ip' => $this->user->ip, + 'attempt_browser' => trim(substr($this->user->browser, 0, 149)), + 'attempt_forwarded_for' => $this->user->forwarded_for, + 'attempt_time' => time(), + 'user_id' => ($row) ? (int) $row['user_id'] : 0, + 'username' => $username, + 'username_clean' => $username_clean, + ); + $sql = 'INSERT INTO ' . LOGIN_ATTEMPT_TABLE . $this->db->sql_build_array('INSERT', $attempt_data); + $result = $this->db->sql_query($sql); + } + else + { + $attempts = 0; + } + + if (!$row) + { + if ($this->config['ip_login_limit_max'] && $attempts >= $this->config['ip_login_limit_max']) + { + return array( + 'status' => LOGIN_ERROR_ATTEMPTS, + 'error_msg' => 'LOGIN_ERROR_ATTEMPTS', + 'user_row' => array('user_id' => ANONYMOUS), + ); + } + + return array( + 'status' => LOGIN_ERROR_USERNAME, + 'error_msg' => 'LOGIN_ERROR_USERNAME', + 'user_row' => array('user_id' => ANONYMOUS), + ); + } + + $show_captcha = ($this->config['max_login_attempts'] && $row['user_login_attempts'] >= $this->config['max_login_attempts']) || + ($this->config['ip_login_limit_max'] && $attempts >= $this->config['ip_login_limit_max']); + + // If there are too many login attempts, we need to check for a confirm image + // Every auth module is able to define what to do by itself... + if ($show_captcha) + { + // Visual Confirmation handling + if (!class_exists('phpbb_captcha_factory', false)) + { + include ($this->phpbb_root_path . 'includes/captcha/captcha_factory.' . $this->php_ext); + } + + $captcha = phpbb_captcha_factory::get_instance($this->config['captcha_plugin']); + $captcha->init(CONFIRM_LOGIN); + $vc_response = $captcha->validate($row); + if ($vc_response) + { + return array( + 'status' => LOGIN_ERROR_ATTEMPTS, + 'error_msg' => 'LOGIN_ERROR_ATTEMPTS', + 'user_row' => $row, + ); + } + else + { + $captcha->reset(); + } + + } + + // If the password convert flag is set we need to convert it + if ($row['user_pass_convert']) + { + // enable super globals to get literal value + // this is needed to prevent unicode normalization + $super_globals_disabled = $this->request->super_globals_disabled(); + if ($super_globals_disabled) + { + $this->request->enable_super_globals(); + } + + // in phpBB2 passwords were used exactly as they were sent, with addslashes applied + $password_old_format = isset($_REQUEST['password']) ? (string) $_REQUEST['password'] : ''; + $password_old_format = (!STRIP) ? addslashes($password_old_format) : $password_old_format; + $password_new_format = $this->request->variable('password', '', true); + + if ($super_globals_disabled) + { + $this->request->disable_super_globals(); + } + + if ($password == $password_new_format) + { + if (!function_exists('utf8_to_cp1252')) + { + include($this->phpbb_root_path . 'includes/utf/data/recode_basic.' . $this->php_ext); + } + + // cp1252 is phpBB2's default encoding, characters outside ASCII range might work when converted into that encoding + // plain md5 support left in for conversions from other systems. + if ((strlen($row['user_password']) == 34 && (phpbb_check_hash(md5($password_old_format), $row['user_password']) || phpbb_check_hash(md5(utf8_to_cp1252($password_old_format)), $row['user_password']))) + || (strlen($row['user_password']) == 32 && (md5($password_old_format) == $row['user_password'] || md5(utf8_to_cp1252($password_old_format)) == $row['user_password']))) + { + $hash = phpbb_hash($password_new_format); + + // Update the password in the users table to the new format and remove user_pass_convert flag + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_password = \'' . $this->db->sql_escape($hash) . '\', + user_pass_convert = 0 + WHERE user_id = ' . $row['user_id']; + $this->db->sql_query($sql); + + $row['user_pass_convert'] = 0; + $row['user_password'] = $hash; + } + else + { + // Although we weren't able to convert this password we have to + // increase login attempt count to make sure this cannot be exploited + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_login_attempts = user_login_attempts + 1 + WHERE user_id = ' . (int) $row['user_id'] . ' + AND user_login_attempts < ' . LOGIN_ATTEMPTS_MAX; + $this->db->sql_query($sql); + + return array( + 'status' => LOGIN_ERROR_PASSWORD_CONVERT, + 'error_msg' => 'LOGIN_ERROR_PASSWORD_CONVERT', + 'user_row' => $row, + ); + } + } + } + + // Check password ... + if (!$row['user_pass_convert'] && phpbb_check_hash($password, $row['user_password'])) + { + // Check for old password hash... + if (strlen($row['user_password']) == 32) + { + $hash = phpbb_hash($password); + + // Update the password in the users table to the new format + $sql = 'UPDATE ' . USERS_TABLE . " + SET user_password = '" . $this->db->sql_escape($hash) . "', + user_pass_convert = 0 + WHERE user_id = {$row['user_id']}"; + $this->db->sql_query($sql); + + $row['user_password'] = $hash; + } + + $sql = 'DELETE FROM ' . LOGIN_ATTEMPT_TABLE . ' + WHERE user_id = ' . $row['user_id']; + $this->db->sql_query($sql); + + if ($row['user_login_attempts'] != 0) + { + // Successful, reset login attempts (the user passed all stages) + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_login_attempts = 0 + WHERE user_id = ' . $row['user_id']; + $this->db->sql_query($sql); + } + + // User inactive... + if ($row['user_type'] == USER_INACTIVE || $row['user_type'] == USER_IGNORE) + { + return array( + 'status' => LOGIN_ERROR_ACTIVE, + 'error_msg' => 'ACTIVE_ERROR', + 'user_row' => $row, + ); + } + + // Successful login... set user_login_attempts to zero... + return array( + 'status' => LOGIN_SUCCESS, + 'error_msg' => false, + 'user_row' => $row, + ); + } + + // Password incorrect - increase login attempts + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_login_attempts = user_login_attempts + 1 + WHERE user_id = ' . (int) $row['user_id'] . ' + AND user_login_attempts < ' . LOGIN_ATTEMPTS_MAX; + $this->db->sql_query($sql); + + // Give status about wrong password... + return array( + 'status' => ($show_captcha) ? LOGIN_ERROR_ATTEMPTS : LOGIN_ERROR_PASSWORD, + 'error_msg' => ($show_captcha) ? 'LOGIN_ERROR_ATTEMPTS' : 'LOGIN_ERROR_PASSWORD', + 'user_row' => $row, + ); + } +} diff --git a/phpBB/phpbb/auth/provider/index.htm b/phpBB/phpbb/auth/provider/index.htm new file mode 100644 index 0000000000..ee1f723a7d --- /dev/null +++ b/phpBB/phpbb/auth/provider/index.htm @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/phpBB/phpbb/auth/provider/interface.php b/phpBB/phpbb/auth/provider/interface.php new file mode 100644 index 0000000000..47043bc107 --- /dev/null +++ b/phpBB/phpbb/auth/provider/interface.php @@ -0,0 +1,105 @@ + status constant + * 'error_msg' => string + * 'user_row' => array + * ) + */ + public function login($username, $password); + + /** + * Autologin function + * + * @return array|null containing the user row, empty if no auto login + * should take place, or null if not impletmented. + */ + public function autologin(); + + /** + * This function is used to output any required fields in the authentication + * admin panel. It also defines any required configuration table fields. + * + * @return array|null Returns null if not implemented or an array of the + * configuration fields of the provider. + */ + public function acp(); + + /** + * This function updates the template with variables related to the acp + * options with whatever configuraton values are passed to it as an array. + * It then returns the name of the acp file related to this authentication + * provider. + * @param array $new_config Contains the new configuration values that + * have been set in acp_board. + * @return array|null Returns null if not implemented or an array with + * the template file name and an array of the vars + * that the template needs that must conform to the + * following example: + * array( + * 'TEMPLATE_FILE' => string, + * 'TEMPLATE_VARS' => array(...), + * ) + */ + public function get_acp_template($new_config); + + /** + * Performs additional actions during logout. + * + * @param array $data An array corresponding to + * phpbb_session::data + * @param boolean $new_session True for a new session, false for no new + * session. + */ + public function logout($data, $new_session); + + /** + * The session validation function checks whether the user is still logged + * into phpBB. + * + * @param array $user + * @return boolean true if the given user is authenticated, false if the + * session should be closed, or null if not implemented. + */ + public function validate_session($user); +} diff --git a/phpBB/phpbb/auth/provider/ldap.php b/phpBB/phpbb/auth/provider/ldap.php new file mode 100644 index 0000000000..0196529408 --- /dev/null +++ b/phpBB/phpbb/auth/provider/ldap.php @@ -0,0 +1,346 @@ +db = $db; + $this->config = $config; + $this->user = $user; + } + + /** + * {@inheritdoc} + */ + public function init() + { + if (!@extension_loaded('ldap')) + { + return $this->user->lang['LDAP_NO_LDAP_EXTENSION']; + } + + $this->config['ldap_port'] = (int) $this->config['ldap_port']; + if ($this->config['ldap_port']) + { + $ldap = @ldap_connect($this->config['ldap_server'], $this->config['ldap_port']); + } + else + { + $ldap = @ldap_connect($this->config['ldap_server']); + } + + if (!$ldap) + { + return $this->user->lang['LDAP_NO_SERVER_CONNECTION']; + } + + @ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3); + @ldap_set_option($ldap, LDAP_OPT_REFERRALS, 0); + + if ($this->config['ldap_user'] || $this->config['ldap_password']) + { + if (!@ldap_bind($ldap, htmlspecialchars_decode($this->config['ldap_user']), htmlspecialchars_decode($this->config['ldap_password']))) + { + return $this->user->lang['LDAP_INCORRECT_USER_PASSWORD']; + } + } + + // ldap_connect only checks whether the specified server is valid, so the connection might still fail + $search = @ldap_search( + $ldap, + htmlspecialchars_decode($this->config['ldap_base_dn']), + $this->ldap_user_filter($this->user->data['username']), + (empty($this->config['ldap_email'])) ? + array(htmlspecialchars_decode($this->config['ldap_uid'])) : + array(htmlspecialchars_decode($this->config['ldap_uid']), htmlspecialchars_decode($this->config['ldap_email'])), + 0, + 1 + ); + + if ($search === false) + { + return $this->user->lang['LDAP_SEARCH_FAILED']; + } + + $result = @ldap_get_entries($ldap, $search); + + @ldap_close($ldap); + + + if (!is_array($result) || sizeof($result) < 2) + { + return sprintf($this->user->lang['LDAP_NO_IDENTITY'], $this->user->data['username']); + } + + if (!empty($this->config['ldap_email']) && !isset($result[0][htmlspecialchars_decode($this->config['ldap_email'])])) + { + return $this->user->lang['LDAP_NO_EMAIL']; + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function login($username, $password) + { + // do not allow empty password + if (!$password) + { + return array( + 'status' => LOGIN_ERROR_PASSWORD, + 'error_msg' => 'NO_PASSWORD_SUPPLIED', + 'user_row' => array('user_id' => ANONYMOUS), + ); + } + + if (!$username) + { + return array( + 'status' => LOGIN_ERROR_USERNAME, + 'error_msg' => 'LOGIN_ERROR_USERNAME', + 'user_row' => array('user_id' => ANONYMOUS), + ); + } + + if (!@extension_loaded('ldap')) + { + return array( + 'status' => LOGIN_ERROR_EXTERNAL_AUTH, + 'error_msg' => 'LDAP_NO_LDAP_EXTENSION', + 'user_row' => array('user_id' => ANONYMOUS), + ); + } + + $this->config['ldap_port'] = (int) $this->config['ldap_port']; + if ($this->config['ldap_port']) + { + $ldap = @ldap_connect($this->config['ldap_server'], $this->config['ldap_port']); + } + else + { + $ldap = @ldap_connect($this->config['ldap_server']); + } + + if (!$ldap) + { + return array( + 'status' => LOGIN_ERROR_EXTERNAL_AUTH, + 'error_msg' => 'LDAP_NO_SERVER_CONNECTION', + 'user_row' => array('user_id' => ANONYMOUS), + ); + } + + @ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3); + @ldap_set_option($ldap, LDAP_OPT_REFERRALS, 0); + + if ($this->config['ldap_user'] || $this->config['ldap_password']) + { + if (!@ldap_bind($ldap, htmlspecialchars_decode($this->config['ldap_user']), htmlspecialchars_decode($this->config['ldap_password']))) + { + return array( + 'status' => LOGIN_ERROR_EXTERNAL_AUTH, + 'error_msg' => 'LDAP_NO_SERVER_CONNECTION', + 'user_row' => array('user_id' => ANONYMOUS), + ); + } + } + + $search = @ldap_search( + $ldap, + htmlspecialchars_decode($this->config['ldap_base_dn']), + $this->ldap_user_filter($username), + (empty($this->config['ldap_email'])) ? + array(htmlspecialchars_decode($this->config['ldap_uid'])) : + array(htmlspecialchars_decode($this->config['ldap_uid']), htmlspecialchars_decode($this->config['ldap_email'])), + 0, + 1 + ); + + $ldap_result = @ldap_get_entries($ldap, $search); + + if (is_array($ldap_result) && sizeof($ldap_result) > 1) + { + if (@ldap_bind($ldap, $ldap_result[0]['dn'], htmlspecialchars_decode($password))) + { + @ldap_close($ldap); + + $sql ='SELECT user_id, username, user_password, user_passchg, user_email, user_type + FROM ' . USERS_TABLE . " + WHERE username_clean = '" . $this->db->sql_escape(utf8_clean_string($username)) . "'"; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if ($row) + { + unset($ldap_result); + + // User inactive... + if ($row['user_type'] == USER_INACTIVE || $row['user_type'] == USER_IGNORE) + { + return array( + 'status' => LOGIN_ERROR_ACTIVE, + 'error_msg' => 'ACTIVE_ERROR', + 'user_row' => $row, + ); + } + + // Successful login... set user_login_attempts to zero... + return array( + 'status' => LOGIN_SUCCESS, + 'error_msg' => false, + 'user_row' => $row, + ); + } + else + { + // retrieve default group id + $sql = 'SELECT group_id + FROM ' . GROUPS_TABLE . " + WHERE group_name = '" . $this->db->sql_escape('REGISTERED') . "' + AND group_type = " . GROUP_SPECIAL; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if (!$row) + { + trigger_error('NO_GROUP'); + } + + // generate user account data + $ldap_user_row = array( + 'username' => $username, + 'user_password' => phpbb_hash($password), + 'user_email' => (!empty($this->config['ldap_email'])) ? utf8_htmlspecialchars($ldap_result[0][htmlspecialchars_decode($this->config['ldap_email'])][0]) : '', + 'group_id' => (int) $row['group_id'], + 'user_type' => USER_NORMAL, + 'user_ip' => $this->user->ip, + 'user_new' => ($this->config['new_member_post_limit']) ? 1 : 0, + ); + + unset($ldap_result); + + // this is the user's first login so create an empty profile + return array( + 'status' => LOGIN_SUCCESS_CREATE_PROFILE, + 'error_msg' => false, + 'user_row' => $ldap_user_row, + ); + } + } + else + { + unset($ldap_result); + @ldap_close($ldap); + + // Give status about wrong password... + return array( + 'status' => LOGIN_ERROR_PASSWORD, + 'error_msg' => 'LOGIN_ERROR_PASSWORD', + 'user_row' => array('user_id' => ANONYMOUS), + ); + } + } + + @ldap_close($ldap); + + return array( + 'status' => LOGIN_ERROR_USERNAME, + 'error_msg' => 'LOGIN_ERROR_USERNAME', + 'user_row' => array('user_id' => ANONYMOUS), + ); + } + + /** + * {@inheritdoc} + */ + + public function acp() + { + // These are fields required in the config table + return array( + 'ldap_server', 'ldap_port', 'ldap_base_dn', 'ldap_uid', 'ldap_user_filter', 'ldap_email', 'ldap_user', 'ldap_password', + ); + } + + /** + * {@inheritdoc} + */ + public function get_acp_template($new_config) + { + return array( + 'TEMPLATE_FILE' => 'auth_provider_ldap.html', + 'TEMPLATE_VARS' => array( + 'AUTH_LDAP_DN' => $new_config['ldap_base_dn'], + 'AUTH_LDAP_EMAIL' => $new_config['ldap_email'], + 'AUTH_LDAP_PASSORD' => $new_config['ldap_password'], + 'AUTH_LDAP_PORT' => $new_config['ldap_port'], + 'AUTH_LDAP_SERVER' => $new_config['ldap_server'], + 'AUTH_LDAP_UID' => $new_config['ldap_uid'], + 'AUTH_LDAP_USER' => $new_config['ldap_user'], + 'AUTH_LDAP_USER_FILTER' => $new_config['ldap_user_filter'], + ), + ); + } + + /** + * Generates a filter string for ldap_search to find a user + * + * @param $username string Username identifying the searched user + * + * @return string A filter string for ldap_search + */ + private function ldap_user_filter($username) + { + $filter = '(' . $this->config['ldap_uid'] . '=' . $this->ldap_escape(htmlspecialchars_decode($username)) . ')'; + if ($this->config['ldap_user_filter']) + { + $_filter = ($this->config['ldap_user_filter'][0] == '(' && substr($this->config['ldap_user_filter'], -1) == ')') ? $this->config['ldap_user_filter'] : "({$this->config['ldap_user_filter']})"; + $filter = "(&{$filter}{$_filter})"; + } + return $filter; + } + + /** + * Escapes an LDAP AttributeValue + * + * @param string $string The string to be escaped + * @return string The escaped string + */ + private function ldap_escape($string) + { + return str_replace(array('*', '\\', '(', ')'), array('\\*', '\\\\', '\\(', '\\)'), $string); + } +} diff --git a/phpBB/phpbb/avatar/driver/driver.php b/phpBB/phpbb/avatar/driver/driver.php new file mode 100644 index 0000000000..29c58d4e62 --- /dev/null +++ b/phpBB/phpbb/avatar/driver/driver.php @@ -0,0 +1,138 @@ +config = $config; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + $this->cache = $cache; + } + + /** + * @inheritdoc + */ + public function get_custom_html($user, $row, $alt = '') + { + return ''; + } + + /** + * @inheritdoc + */ + public function prepare_form_acp($user) + { + return array(); + } + + /** + * @inheritdoc + */ + public function delete($row) + { + return true; + } + + /** + * @inheritdoc + */ + public function get_template_name() + { + $driver = preg_replace('#^phpbb_avatar_driver_#', '', get_class($this)); + $template = "ucp_avatar_options_$driver.html"; + + return $template; + } + + /** + * @inheritdoc + */ + public function get_name() + { + return $this->name; + } + + /** + * Sets the name of the driver. + * + * @param string $name Driver name + */ + public function set_name($name) + { + $this->name = $name; + } +} diff --git a/phpBB/phpbb/avatar/driver/gravatar.php b/phpBB/phpbb/avatar/driver/gravatar.php new file mode 100644 index 0000000000..d559da1c0d --- /dev/null +++ b/phpBB/phpbb/avatar/driver/gravatar.php @@ -0,0 +1,172 @@ + $row['avatar'], + 'width' => $row['avatar_width'], + 'height' => $row['avatar_height'], + ); + } + + /** + * @inheritdoc + */ + public function get_custom_html($user, $row, $alt = '') + { + return ''; + } + + /** + * @inheritdoc + */ + public function prepare_form($request, $template, $user, $row, &$error) + { + $template->assign_vars(array( + 'AVATAR_GRAVATAR_WIDTH' => (($row['avatar_type'] == $this->get_name() || $row['avatar_type'] == 'gravatar') && $row['avatar_width']) ? $row['avatar_width'] : $request->variable('avatar_gravatar_width', 0), + 'AVATAR_GRAVATAR_HEIGHT' => (($row['avatar_type'] == $this->get_name() || $row['avatar_type'] == 'gravatar') && $row['avatar_height']) ? $row['avatar_height'] : $request->variable('avatar_gravatar_width', 0), + 'AVATAR_GRAVATAR_EMAIL' => (($row['avatar_type'] == $this->get_name() || $row['avatar_type'] == 'gravatar') && $row['avatar']) ? $row['avatar'] : '', + )); + + return true; + } + + /** + * @inheritdoc + */ + public function process_form($request, $template, $user, $row, &$error) + { + $row['avatar'] = $request->variable('avatar_gravatar_email', ''); + $row['avatar_width'] = $request->variable('avatar_gravatar_width', 0); + $row['avatar_height'] = $request->variable('avatar_gravatar_height', 0); + + if (!function_exists('validate_data')) + { + require($this->phpbb_root_path . 'includes/functions_user.' . $this->php_ext); + } + + $validate_array = validate_data( + array( + 'email' => $row['avatar'], + ), + array( + 'email' => array( + array('string', false, 6, 60), + array('email')) + ) + ); + + $error = array_merge($error, $validate_array); + + if (!empty($error)) + { + return false; + } + + // Make sure getimagesize works... + if (function_exists('getimagesize') && ($row['avatar_width'] <= 0 || $row['avatar_height'] <= 0)) + { + /** + * default to the minimum of the maximum allowed avatar size if the size + * is not or only partially entered + */ + $row['avatar_width'] = $row['avatar_height'] = min($this->config['avatar_max_width'], $this->config['avatar_max_height']); + $url = $this->get_gravatar_url($row); + + if (($row['avatar_width'] <= 0 || $row['avatar_height'] <= 0) && (($image_data = getimagesize($url)) === false)) + { + $error[] = 'UNABLE_GET_IMAGE_SIZE'; + return false; + } + + if (!empty($image_data) && ($image_data[0] <= 0 || $image_data[1] <= 0)) + { + $error[] = 'AVATAR_NO_SIZE'; + return false; + } + + $row['avatar_width'] = ($row['avatar_width'] && $row['avatar_height']) ? $row['avatar_width'] : $image_data[0]; + $row['avatar_height'] = ($row['avatar_width'] && $row['avatar_height']) ? $row['avatar_height'] : $image_data[1]; + } + + if ($row['avatar_width'] <= 0 || $row['avatar_height'] <= 0) + { + $error[] = 'AVATAR_NO_SIZE'; + return false; + } + + if ($this->config['avatar_max_width'] || $this->config['avatar_max_height']) + { + if ($row['avatar_width'] > $this->config['avatar_max_width'] || $row['avatar_height'] > $this->config['avatar_max_height']) + { + $error[] = array('AVATAR_WRONG_SIZE', $this->config['avatar_min_width'], $this->config['avatar_min_height'], $this->config['avatar_max_width'], $this->config['avatar_max_height'], $row['avatar_width'], $row['avatar_height']); + return false; + } + } + + if ($this->config['avatar_min_width'] || $this->config['avatar_min_height']) + { + if ($row['avatar_width'] < $this->config['avatar_min_width'] || $row['avatar_height'] < $this->config['avatar_min_height']) + { + $error[] = array('AVATAR_WRONG_SIZE', $this->config['avatar_min_width'], $this->config['avatar_min_height'], $this->config['avatar_max_width'], $this->config['avatar_max_height'], $row['avatar_width'], $row['avatar_height']); + return false; + } + } + + return array( + 'avatar' => $row['avatar'], + 'avatar_width' => $row['avatar_width'], + 'avatar_height' => $row['avatar_height'], + ); + } + + /** + * Build gravatar URL for output on page + * + * @return string Gravatar URL + */ + protected function get_gravatar_url($row) + { + $url = self::GRAVATAR_URL; + $url .= md5(strtolower(trim($row['avatar']))); + + if ($row['avatar_width'] || $row['avatar_height']) + { + $url .= '?s=' . max($row['avatar_width'], $row['avatar_height']); + } + + return $url; + } +} diff --git a/phpBB/phpbb/avatar/driver/interface.php b/phpBB/phpbb/avatar/driver/interface.php new file mode 100644 index 0000000000..3d62969aef --- /dev/null +++ b/phpBB/phpbb/avatar/driver/interface.php @@ -0,0 +1,116 @@ + '', 'width' => 0, 'height' => 0] + */ + public function get_data($row); + + /** + * Returns custom html if it is needed for displaying this avatar + * + * @param phpbb_user $user phpBB user object + * @param array $row User data or group data that has been cleaned with + * phpbb_avatar_manager::clean_row + * @param string $alt Alternate text for avatar image + * + * @return string HTML + */ + public function get_custom_html($user, $row, $alt = ''); + + /** + * Prepare form for changing the settings of this avatar + * + * @param phpbb_request $request Request object + * @param phpbb_template $template Template object + * @param phpbb_user $user User object + * @param array $row User data or group data that has been cleaned with + * phpbb_avatar_manager::clean_row + * @param array &$error Reference to an error array that is filled by this + * function. Key values can either be a string with a language key or + * an array that will be passed to vsprintf() with the language key in + * the first array key. + * + * @return bool True if form has been successfully prepared + */ + public function prepare_form($request, $template, $user, $row, &$error); + + /** + * Prepare form for changing the acp settings of this avatar + * + * @param phpbb_user $user phpBB user object + * + * @return array Array of configuration options as consumed by acp_board. + * The setting for enabling/disabling the avatar will be handled by + * the avatar manager. + */ + public function prepare_form_acp($user); + + /** + * Process form data + * + * @param phpbb_request $request Request object + * @param phpbb_template $template Template object + * @param phpbb_user $user User object + * @param array $row User data or group data that has been cleaned with + * phpbb_avatar_manager::clean_row + * @param array &$error Reference to an error array that is filled by this + * function. Key values can either be a string with a language key or + * an array that will be passed to vsprintf() with the language key in + * the first array key. + * + * @return array Array containing the avatar data as follows: + * ['avatar'], ['avatar_width'], ['avatar_height'] + */ + public function process_form($request, $template, $user, $row, &$error); + + /** + * Delete avatar + * + * @param array $row User data or group data that has been cleaned with + * phpbb_avatar_manager::clean_row + * + * @return bool True if avatar has been deleted or there is no need to delete, + * i.e. when the avatar is not hosted locally. + */ + public function delete($row); + + /** + * Get the avatar driver's template name + * + * @return string Avatar driver's template name + */ + public function get_template_name(); +} diff --git a/phpBB/phpbb/avatar/driver/local.php b/phpBB/phpbb/avatar/driver/local.php new file mode 100644 index 0000000000..f4bcd4ce74 --- /dev/null +++ b/phpBB/phpbb/avatar/driver/local.php @@ -0,0 +1,197 @@ + $this->phpbb_root_path . $this->config['avatar_gallery_path'] . '/' . $row['avatar'], + 'width' => $row['avatar_width'], + 'height' => $row['avatar_height'], + ); + } + + /** + * @inheritdoc + */ + public function prepare_form($request, $template, $user, $row, &$error) + { + $avatar_list = $this->get_avatar_list($user); + $category = $request->variable('avatar_local_cat', ''); + + foreach ($avatar_list as $cat => $null) + { + if (!empty($avatar_list[$cat])) + { + $template->assign_block_vars('avatar_local_cats', array( + 'NAME' => $cat, + 'SELECTED' => ($cat == $category), + )); + } + + if ($cat != $category) + { + unset($avatar_list[$cat]); + } + } + + if (!empty($avatar_list[$category])) + { + $template->assign_vars(array( + 'AVATAR_LOCAL_SHOW' => true, + )); + + $table_cols = isset($row['avatar_gallery_cols']) ? $row['avatar_gallery_cols'] : 4; + $row_count = $col_count = $avatar_pos = 0; + $avatar_count = sizeof($avatar_list[$category]); + + reset($avatar_list[$category]); + + while ($avatar_pos < $avatar_count) + { + $img = current($avatar_list[$category]); + next($avatar_list[$category]); + + if ($col_count == 0) + { + ++$row_count; + $template->assign_block_vars('avatar_local_row', array( + )); + } + + $template->assign_block_vars('avatar_local_row.avatar_local_col', array( + 'AVATAR_IMAGE' => $this->phpbb_root_path . $this->config['avatar_gallery_path'] . '/' . $img['file'], + 'AVATAR_NAME' => $img['name'], + 'AVATAR_FILE' => $img['filename'], + )); + + $template->assign_block_vars('avatar_local_row.avatar_local_option', array( + 'AVATAR_FILE' => $img['filename'], + 'S_OPTIONS_AVATAR' => $img['filename'] + )); + + $col_count = ($col_count + 1) % $table_cols; + + ++$avatar_pos; + } + } + + return true; + } + + /** + * @inheritdoc + */ + public function prepare_form_acp($user) + { + return array( + 'avatar_gallery_path' => array('lang' => 'AVATAR_GALLERY_PATH', 'validate' => 'rpath', 'type' => 'text:20:255', 'explain' => true), + ); + } + + /** + * @inheritdoc + */ + public function process_form($request, $template, $user, $row, &$error) + { + $avatar_list = $this->get_avatar_list($user); + $category = $request->variable('avatar_local_cat', ''); + + $file = $request->variable('avatar_local_file', ''); + + if (empty($category) || empty($file)) + { + $error[] = 'NO_AVATAR_SELECTED'; + return false; + } + + if (!isset($avatar_list[$category][urldecode($file)])) + { + $error[] = 'AVATAR_URL_NOT_FOUND'; + return false; + } + + return array( + 'avatar' => ($category != $user->lang['MAIN']) ? $category . '/' . $file : $file, + 'avatar_width' => $avatar_list[$category][urldecode($file)]['width'], + 'avatar_height' => $avatar_list[$category][urldecode($file)]['height'], + ); + } + + /** + * Get a list of avatars that are locally available + * Results get cached for 24 hours (86400 seconds) + * + * @param phpbb_user $user User object + * + * @return array Array containing the locally available avatars + */ + protected function get_avatar_list($user) + { + $avatar_list = ($this->cache == null) ? false : $this->cache->get('avatar_local_list'); + + if ($avatar_list === false) + { + $avatar_list = array(); + $path = $this->phpbb_root_path . $this->config['avatar_gallery_path']; + + $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS | FilesystemIterator::UNIX_PATHS), RecursiveIteratorIterator::SELF_FIRST); + foreach ($iterator as $file_info) + { + $file_path = $file_info->getPath(); + $image = $file_info->getFilename(); + + // Match all images in the gallery folder + if (preg_match('#^[^&\'"<>]+\.(?:' . implode('|', $this->allowed_extensions) . ')$#i', $image) && is_file($file_path . '/' . $image)) + { + if (function_exists('getimagesize')) + { + $dims = getimagesize($file_path . '/' . $image); + } + else + { + $dims = array(0, 0); + } + $cat = ($path == $file_path) ? $user->lang['MAIN'] : str_replace("$path/", '', $file_path); + $avatar_list[$cat][$image] = array( + 'file' => ($cat != $user->lang['MAIN']) ? rawurlencode($cat) . '/' . rawurlencode($image) : rawurlencode($image), + 'filename' => rawurlencode($image), + 'name' => ucfirst(str_replace('_', ' ', preg_replace('#^(.*)\..*$#', '\1', $image))), + 'width' => $dims[0], + 'height' => $dims[1], + ); + } + } + ksort($avatar_list); + + if ($this->cache != null) + { + $this->cache->put('avatar_local_list', $avatar_list, 86400); + } + } + + return $avatar_list; + } +} diff --git a/phpBB/phpbb/avatar/driver/remote.php b/phpBB/phpbb/avatar/driver/remote.php new file mode 100644 index 0000000000..7da58107a1 --- /dev/null +++ b/phpBB/phpbb/avatar/driver/remote.php @@ -0,0 +1,164 @@ + $row['avatar'], + 'width' => $row['avatar_width'], + 'height' => $row['avatar_height'], + ); + } + + /** + * @inheritdoc + */ + public function prepare_form($request, $template, $user, $row, &$error) + { + $template->assign_vars(array( + 'AVATAR_REMOTE_WIDTH' => ((in_array($row['avatar_type'], array(AVATAR_REMOTE, $this->get_name(), 'remote'))) && $row['avatar_width']) ? $row['avatar_width'] : $request->variable('avatar_remote_width', 0), + 'AVATAR_REMOTE_HEIGHT' => ((in_array($row['avatar_type'], array(AVATAR_REMOTE, $this->get_name(), 'remote'))) && $row['avatar_height']) ? $row['avatar_height'] : $request->variable('avatar_remote_width', 0), + 'AVATAR_REMOTE_URL' => ((in_array($row['avatar_type'], array(AVATAR_REMOTE, $this->get_name(), 'remote'))) && $row['avatar']) ? $row['avatar'] : '', + )); + + return true; + } + + /** + * @inheritdoc + */ + public function process_form($request, $template, $user, $row, &$error) + { + $url = $request->variable('avatar_remote_url', ''); + $width = $request->variable('avatar_remote_width', 0); + $height = $request->variable('avatar_remote_height', 0); + + if (!preg_match('#^(http|https|ftp)://#i', $url)) + { + $url = 'http://' . $url; + } + + if (!function_exists('validate_data')) + { + require($this->phpbb_root_path . 'includes/functions_user.' . $this->php_ext); + } + + $validate_array = validate_data( + array( + 'url' => $url, + ), + array( + 'url' => array('string', true, 5, 255), + ) + ); + + $error = array_merge($error, $validate_array); + + if (!empty($error)) + { + return false; + } + + // Check if this url looks alright + // This isn't perfect, but it's what phpBB 3.0 did, and might as well make sure everything is compatible + if (!preg_match('#^(http|https|ftp)://(?:(.*?\.)*?[a-z0-9\-]+?\.[a-z]{2,4}|(?:\d{1,3}\.){3,5}\d{1,3}):?([0-9]*?).*?\.('. implode('|', $this->allowed_extensions) . ')$#i', $url)) + { + $error[] = 'AVATAR_URL_INVALID'; + return false; + } + + // Make sure getimagesize works... + if (function_exists('getimagesize')) + { + if (($width <= 0 || $height <= 0) && (($image_data = getimagesize($url)) === false)) + { + $error[] = 'UNABLE_GET_IMAGE_SIZE'; + return false; + } + + if (!empty($image_data) && ($image_data[0] <= 0 || $image_data[1] <= 0)) + { + $error[] = 'AVATAR_NO_SIZE'; + return false; + } + + $width = ($width && $height) ? $width : $image_data[0]; + $height = ($width && $height) ? $height : $image_data[1]; + } + + if ($width <= 0 || $height <= 0) + { + $error[] = 'AVATAR_NO_SIZE'; + return false; + } + + if (!class_exists('fileupload')) + { + include($this->phpbb_root_path . 'includes/functions_upload.' . $this->php_ext); + } + + $types = fileupload::image_types(); + $extension = strtolower(filespec::get_extension($url)); + + if (!empty($image_data) && (!isset($types[$image_data[2]]) || !in_array($extension, $types[$image_data[2]]))) + { + if (!isset($types[$image_data[2]])) + { + $error[] = 'UNABLE_GET_IMAGE_SIZE'; + } + else + { + $error[] = array('IMAGE_FILETYPE_MISMATCH', $types[$image_data[2]][0], $extension); + } + + return false; + } + + if ($this->config['avatar_max_width'] || $this->config['avatar_max_height']) + { + if ($width > $this->config['avatar_max_width'] || $height > $this->config['avatar_max_height']) + { + $error[] = array('AVATAR_WRONG_SIZE', $this->config['avatar_min_width'], $this->config['avatar_min_height'], $this->config['avatar_max_width'], $this->config['avatar_max_height'], $width, $height); + return false; + } + } + + if ($this->config['avatar_min_width'] || $this->config['avatar_min_height']) + { + if ($width < $this->config['avatar_min_width'] || $height < $this->config['avatar_min_height']) + { + $error[] = array('AVATAR_WRONG_SIZE', $this->config['avatar_min_width'], $this->config['avatar_min_height'], $this->config['avatar_max_width'], $this->config['avatar_max_height'], $width, $height); + return false; + } + } + + return array( + 'avatar' => $url, + 'avatar_width' => $width, + 'avatar_height' => $height, + ); + } +} diff --git a/phpBB/phpbb/avatar/driver/upload.php b/phpBB/phpbb/avatar/driver/upload.php new file mode 100644 index 0000000000..685ac4f349 --- /dev/null +++ b/phpBB/phpbb/avatar/driver/upload.php @@ -0,0 +1,185 @@ + $this->phpbb_root_path . 'download/file.' . $this->php_ext . '?avatar=' . $row['avatar'], + 'width' => $row['avatar_width'], + 'height' => $row['avatar_height'], + ); + } + + /** + * @inheritdoc + */ + public function prepare_form($request, $template, $user, $row, &$error) + { + if (!$this->can_upload()) + { + return false; + } + + $template->assign_vars(array( + 'S_UPLOAD_AVATAR_URL' => ($this->config['allow_avatar_remote_upload']) ? true : false, + 'AVATAR_UPLOAD_SIZE' => $this->config['avatar_filesize'], + )); + + return true; + } + + /** + * @inheritdoc + */ + public function process_form($request, $template, $user, $row, &$error) + { + if (!$this->can_upload()) + { + return false; + } + + if (!class_exists('fileupload')) + { + include($this->phpbb_root_path . 'includes/functions_upload.' . $this->php_ext); + } + + $upload = new fileupload('AVATAR_', $this->allowed_extensions, $this->config['avatar_filesize'], $this->config['avatar_min_width'], $this->config['avatar_min_height'], $this->config['avatar_max_width'], $this->config['avatar_max_height'], (isset($this->config['mime_triggers']) ? explode('|', $this->config['mime_triggers']) : false)); + + $url = $request->variable('avatar_upload_url', ''); + $upload_file = $request->file('avatar_upload_file'); + + if (!empty($upload_file['name'])) + { + $file = $upload->form_upload('avatar_upload_file'); + } + elseif (!empty($this->config['allow_avatar_remote_upload']) && !empty($url)) + { + if (!preg_match('#^(http|https|ftp)://#i', $url)) + { + $url = 'http://' . $url; + } + + if (!function_exists('validate_data')) + { + require($this->phpbb_root_path . 'includes/functions_user.' . $this->php_ext); + } + + $validate_array = validate_data( + array( + 'url' => $url, + ), + array( + 'url' => array('string', true, 5, 255), + ) + ); + + $error = array_merge($error, $validate_array); + + if (!empty($error)) + { + return false; + } + + $file = $upload->remote_upload($url); + } + else + { + $error[] = 'NO_AVATAR_SELECTED'; + return false; + } + + $prefix = $this->config['avatar_salt'] . '_'; + $file->clean_filename('avatar', $prefix, $row['id']); + + $destination = $this->config['avatar_path']; + + // Adjust destination path (no trailing slash) + if (substr($destination, -1, 1) == '/' || substr($destination, -1, 1) == '\\') + { + $destination = substr($destination, 0, -1); + } + + $destination = str_replace(array('../', '..\\', './', '.\\'), '', $destination); + if ($destination && ($destination[0] == '/' || $destination[0] == "\\")) + { + $destination = ''; + } + + // Move file and overwrite any existing image + $file->move_file($destination, true); + + if (sizeof($file->error)) + { + $file->remove(); + $error = array_merge($error, $file->error); + return false; + } + + return array( + 'avatar' => $row['id'] . '_' . time() . '.' . $file->get('extension'), + 'avatar_width' => $file->get('width'), + 'avatar_height' => $file->get('height'), + ); + } + + /** + * @inheritdoc + */ + public function prepare_form_acp($user) + { + return array( + 'allow_avatar_remote_upload'=> array('lang' => 'ALLOW_REMOTE_UPLOAD', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'avatar_filesize' => array('lang' => 'MAX_FILESIZE', 'validate' => 'int:0', 'type' => 'number:0', 'explain' => true, 'append' => ' ' . $user->lang['BYTES']), + 'avatar_path' => array('lang' => 'AVATAR_STORAGE_PATH', 'validate' => 'rwpath', 'type' => 'text:20:255', 'explain' => true), + ); + } + + /** + * @inheritdoc + */ + public function delete($row) + { + $ext = substr(strrchr($row['avatar'], '.'), 1); + $filename = $this->phpbb_root_path . $this->config['avatar_path'] . '/' . $this->config['avatar_salt'] . '_' . $row['id'] . '.' . $ext; + + if (file_exists($filename)) + { + @unlink($filename); + } + + return true; + } + + /** + * Check if user is able to upload an avatar + * + * @return bool True if user can upload, false if not + */ + protected function can_upload() + { + return (file_exists($this->phpbb_root_path . $this->config['avatar_path']) && phpbb_is_writable($this->phpbb_root_path . $this->config['avatar_path']) && (@ini_get('file_uploads') || strtolower(@ini_get('file_uploads')) == 'on')); + } +} diff --git a/phpBB/phpbb/avatar/manager.php b/phpBB/phpbb/avatar/manager.php new file mode 100644 index 0000000000..58d994c3c0 --- /dev/null +++ b/phpBB/phpbb/avatar/manager.php @@ -0,0 +1,309 @@ + '', + 'avatar_type' => '', + 'avatar_width' => '', + 'avatar_height' => '', + ); + + /** + * Construct an avatar manager object + * + * @param phpbb_config $config phpBB configuration + * @param array $avatar_drivers Avatar drivers passed via the service container + * @param object $container Container object + */ + public function __construct(phpbb_config $config, $avatar_drivers, $container) + { + $this->config = $config; + $this->avatar_drivers = $avatar_drivers; + $this->container = $container; + } + + /** + * Get the driver object specified by the avatar type + * + * @param string $avatar_type Avatar type; by default an avatar's service container name + * @param bool $load_enabled Load only enabled avatars + * + * @return object Avatar driver object + */ + public function get_driver($avatar_type, $load_enabled = true) + { + if (self::$enabled_drivers === false) + { + $this->load_enabled_drivers(); + } + + $avatar_drivers = ($load_enabled) ? self::$enabled_drivers : $this->get_all_drivers(); + + // Legacy stuff... + switch ($avatar_type) + { + case AVATAR_GALLERY: + $avatar_type = 'avatar.driver.local'; + break; + case AVATAR_UPLOAD: + $avatar_type = 'avatar.driver.upload'; + break; + case AVATAR_REMOTE: + $avatar_type = 'avatar.driver.remote'; + break; + } + + if (!isset($avatar_drivers[$avatar_type])) + { + return null; + } + + /* + * There is no need to handle invalid avatar types as the following code + * will cause a ServiceNotFoundException if the type does not exist + */ + $driver = $this->container->get($avatar_type); + + return $driver; + } + + /** + * Load the list of enabled drivers + * This is executed once and fills self::$enabled_drivers + */ + protected function load_enabled_drivers() + { + if (!empty($this->avatar_drivers)) + { + self::$enabled_drivers = array(); + foreach ($this->avatar_drivers as $driver) + { + if ($this->is_enabled($driver)) + { + self::$enabled_drivers[$driver->get_name()] = $driver->get_name(); + } + } + asort(self::$enabled_drivers); + } + } + + /** + * Get a list of all avatar drivers + * + * As this function will only be called in the ACP avatar settings page, it + * doesn't make much sense to cache the list of all avatar drivers like the + * list of the enabled drivers. + * + * @return array Array containing a list of all avatar drivers + */ + public function get_all_drivers() + { + $drivers = array(); + + if (!empty($this->avatar_drivers)) + { + foreach ($this->avatar_drivers as $driver) + { + $drivers[$driver->get_name()] = $driver->get_name(); + } + asort($drivers); + } + + return $drivers; + } + + /** + * Get a list of enabled avatar drivers + * + * @return array Array containing a list of the enabled avatar drivers + */ + public function get_enabled_drivers() + { + if (self::$enabled_drivers === false) + { + $this->load_enabled_drivers(); + } + + return self::$enabled_drivers; + } + + /** + * Strip out user_ and group_ prefixes from keys + * + * @param array $row User data or group data + * + * @return array User data or group data with keys that have been + * stripped from the preceding "user_" or "group_" + */ + static public function clean_row($row) + { + // Upon creation of a user/group $row might be empty + if (empty($row)) + { + return self::$default_row; + } + + $keys = array_keys($row); + $values = array_values($row); + + $keys = array_map(array('phpbb_avatar_manager', 'strip_prefix'), $keys); + + return array_combine($keys, $values); + } + + /** + * Strip prepending user_ or group_ prefix from key + * + * @param string Array key + * @return string Key that has been stripped from its prefix + */ + static protected function strip_prefix($key) + { + return preg_replace('#^(?:user_|group_)#', '', $key); + } + + /** + * Clean driver names that are returned from template files + * Underscores are replaced with dots + * + * @param string $name Driver name + * + * @return string Cleaned driver name + */ + static public function clean_driver_name($name) + { + return str_replace('_', '.', $name); + } + + /** + * Prepare driver names for use in template files + * Dots are replaced with underscores + * + * @param string $name Clean driver name + * + * @return string Prepared driver name + */ + static public function prepare_driver_name($name) + { + return str_replace('.', '_', $name); + } + + /** + * Check if avatar is enabled + * + * @param object $driver Avatar driver object + * + * @return bool True if avatar is enabled, false if it's disabled + */ + public function is_enabled($driver) + { + $config_name = $this->get_driver_config_name($driver); + + return $this->config["allow_avatar_{$config_name}"]; + } + + /** + * Get the settings array for enabling/disabling an avatar driver + * + * @param object $driver Avatar driver object + * + * @return array Array of configuration options as consumed by acp_board + */ + public function get_avatar_settings($driver) + { + $config_name = $this->get_driver_config_name($driver); + + return array( + 'allow_avatar_' . $config_name => array('lang' => 'ALLOW_' . strtoupper($config_name), 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => false), + ); + } + + /** + * Get the config name of an avatar driver + * + * @param object $driver Avatar driver object + * + * @return string Avatar driver config name + */ + public function get_driver_config_name($driver) + { + return preg_replace('#^phpbb_avatar_driver_#', '', get_class($driver)); + } + + /** + * Replace "error" strings with their real, localized form + * + * @param phpbb_user phpBB User object + * @param array $error Array containing error strings + * Key values can either be a string with a language key or an array + * that will be passed to vsprintf() with the language key in the + * first array key. + * + * @return array Array containing the localized error strings + */ + public function localize_errors(phpbb_user $user, $error) + { + foreach ($error as $key => $lang) + { + if (is_array($lang)) + { + $lang_key = array_shift($lang); + $error[$key] = vsprintf($user->lang($lang_key), $lang); + } + else + { + $error[$key] = $user->lang("$lang"); + } + } + + return $error; + } +} diff --git a/phpBB/phpbb/cache/driver/apc.php b/phpBB/phpbb/cache/driver/apc.php new file mode 100644 index 0000000000..0516b669c8 --- /dev/null +++ b/phpBB/phpbb/cache/driver/apc.php @@ -0,0 +1,75 @@ +key_prefix . $var); + } + + /** + * Store data in the cache + * + * @access protected + * @param string $var Cache key + * @param mixed $data Data to store + * @param int $ttl Time-to-live of cached data + * @return bool True if the operation succeeded + */ + function _write($var, $data, $ttl = 2592000) + { + return apc_store($this->key_prefix . $var, $data, $ttl); + } + + /** + * Remove an item from the cache + * + * @access protected + * @param string $var Cache key + * @return bool True if the operation succeeded + */ + function _delete($var) + { + return apc_delete($this->key_prefix . $var); + } +} diff --git a/phpBB/phpbb/cache/driver/base.php b/phpBB/phpbb/cache/driver/base.php new file mode 100644 index 0000000000..32e04f813a --- /dev/null +++ b/phpBB/phpbb/cache/driver/base.php @@ -0,0 +1,23 @@ +key_prefix + eaccelerator_rm(substr($var['name'], 1)); + } + + parent::purge(); + } + + /** + * Perform cache garbage collection + * + * @return null + */ + function tidy() + { + eaccelerator_gc(); + + set_config('cache_last_gc', time(), true); + } + + /** + * Fetch an item from the cache + * + * @access protected + * @param string $var Cache key + * @return mixed Cached data + */ + function _read($var) + { + $result = eaccelerator_get($this->key_prefix . $var); + + if ($result === null) + { + return false; + } + + // Handle serialized objects + if (is_string($result) && strpos($result, $this->serialize_header . 'O:') === 0) + { + $result = unserialize(substr($result, strlen($this->serialize_header))); + } + + return $result; + } + + /** + * Store data in the cache + * + * @access protected + * @param string $var Cache key + * @param mixed $data Data to store + * @param int $ttl Time-to-live of cached data + * @return bool True if the operation succeeded + */ + function _write($var, $data, $ttl = 2592000) + { + // Serialize objects and make them easy to detect + $data = (is_object($data)) ? $this->serialize_header . serialize($data) : $data; + + return eaccelerator_put($this->key_prefix . $var, $data, $ttl); + } + + /** + * Remove an item from the cache + * + * @access protected + * @param string $var Cache key + * @return bool True if the operation succeeded + */ + function _delete($var) + { + return eaccelerator_rm($this->key_prefix . $var); + } +} diff --git a/phpBB/phpbb/cache/driver/file.php b/phpBB/phpbb/cache/driver/file.php new file mode 100644 index 0000000000..85decbe3e8 --- /dev/null +++ b/phpBB/phpbb/cache/driver/file.php @@ -0,0 +1,740 @@ +cache_dir = !is_null($cache_dir) ? $cache_dir : $phpbb_root_path . 'cache/'; + } + + /** + * Load global cache + */ + function load() + { + return $this->_read('data_global'); + } + + /** + * Unload cache object + */ + function unload() + { + $this->save(); + unset($this->vars); + unset($this->var_expires); + unset($this->sql_rowset); + unset($this->sql_row_pointer); + + $this->vars = array(); + $this->var_expires = array(); + $this->sql_rowset = array(); + $this->sql_row_pointer = array(); + } + + /** + * Save modified objects + */ + function save() + { + if (!$this->is_modified) + { + return; + } + + global $phpEx; + + if (!$this->_write('data_global')) + { + if (!function_exists('phpbb_is_writable')) + { + global $phpbb_root_path; + include($phpbb_root_path . 'includes/functions.' . $phpEx); + } + + // Now, this occurred how often? ... phew, just tell the user then... + if (!phpbb_is_writable($this->cache_dir)) + { + // We need to use die() here, because else we may encounter an infinite loop (the message handler calls $cache->unload()) + die('Fatal: ' . $this->cache_dir . ' is NOT writable.'); + exit; + } + + die('Fatal: Not able to open ' . $this->cache_dir . 'data_global.' . $phpEx); + exit; + } + + $this->is_modified = false; + } + + /** + * Tidy cache + */ + function tidy() + { + global $phpEx; + + $dir = @opendir($this->cache_dir); + + if (!$dir) + { + return; + } + + $time = time(); + + while (($entry = readdir($dir)) !== false) + { + if (!preg_match('/^(sql_|data_(?!global))/', $entry)) + { + continue; + } + + if (!($handle = @fopen($this->cache_dir . $entry, 'rb'))) + { + continue; + } + + // Skip the PHP header + fgets($handle); + + // Skip expiration + $expires = (int) fgets($handle); + + fclose($handle); + + if ($time >= $expires) + { + $this->remove_file($this->cache_dir . $entry); + } + } + closedir($dir); + + if (file_exists($this->cache_dir . 'data_global.' . $phpEx)) + { + if (!sizeof($this->vars)) + { + $this->load(); + } + + foreach ($this->var_expires as $var_name => $expires) + { + if ($time >= $expires) + { + $this->destroy($var_name); + } + } + } + + set_config('cache_last_gc', time(), true); + } + + /** + * Get saved cache object + */ + function get($var_name) + { + if ($var_name[0] == '_') + { + global $phpEx; + + if (!$this->_exists($var_name)) + { + return false; + } + + return $this->_read('data' . $var_name); + } + else + { + return ($this->_exists($var_name)) ? $this->vars[$var_name] : false; + } + } + + /** + * Put data into cache + */ + function put($var_name, $var, $ttl = 31536000) + { + if ($var_name[0] == '_') + { + $this->_write('data' . $var_name, $var, time() + $ttl); + } + else + { + $this->vars[$var_name] = $var; + $this->var_expires[$var_name] = time() + $ttl; + $this->is_modified = true; + } + } + + /** + * Purge cache data + */ + function purge() + { + // Purge all phpbb cache files + $dir = @opendir($this->cache_dir); + + if (!$dir) + { + return; + } + + while (($entry = readdir($dir)) !== false) + { + if (strpos($entry, 'container_') !== 0 && + strpos($entry, 'url_matcher') !== 0 && + strpos($entry, 'sql_') !== 0 && + strpos($entry, 'data_') !== 0 && + strpos($entry, 'ctpl_') !== 0 && + strpos($entry, 'tpl_') !== 0) + { + continue; + } + + $this->remove_file($this->cache_dir . $entry); + } + closedir($dir); + + unset($this->vars); + unset($this->var_expires); + unset($this->sql_rowset); + unset($this->sql_row_pointer); + + $this->vars = array(); + $this->var_expires = array(); + $this->sql_rowset = array(); + $this->sql_row_pointer = array(); + + $this->is_modified = false; + } + + /** + * Destroy cache data + */ + function destroy($var_name, $table = '') + { + global $phpEx; + + if ($var_name == 'sql' && !empty($table)) + { + if (!is_array($table)) + { + $table = array($table); + } + + $dir = @opendir($this->cache_dir); + + if (!$dir) + { + return; + } + + while (($entry = readdir($dir)) !== false) + { + if (strpos($entry, 'sql_') !== 0) + { + continue; + } + + if (!($handle = @fopen($this->cache_dir . $entry, 'rb'))) + { + continue; + } + + // Skip the PHP header + fgets($handle); + + // Skip expiration + fgets($handle); + + // Grab the query, remove the LF + $query = substr(fgets($handle), 0, -1); + + fclose($handle); + + foreach ($table as $check_table) + { + // Better catch partial table names than no table names. ;) + if (strpos($query, $check_table) !== false) + { + $this->remove_file($this->cache_dir . $entry); + break; + } + } + } + closedir($dir); + + return; + } + + if (!$this->_exists($var_name)) + { + return; + } + + if ($var_name[0] == '_') + { + $this->remove_file($this->cache_dir . 'data' . $var_name . ".$phpEx", true); + } + else if (isset($this->vars[$var_name])) + { + $this->is_modified = true; + unset($this->vars[$var_name]); + unset($this->var_expires[$var_name]); + + // We save here to let the following cache hits succeed + $this->save(); + } + } + + /** + * Check if a given cache entry exist + */ + function _exists($var_name) + { + if ($var_name[0] == '_') + { + global $phpEx; + return file_exists($this->cache_dir . 'data' . $var_name . ".$phpEx"); + } + else + { + if (!sizeof($this->vars)) + { + $this->load(); + } + + if (!isset($this->var_expires[$var_name])) + { + return false; + } + + return (time() > $this->var_expires[$var_name]) ? false : isset($this->vars[$var_name]); + } + } + + /** + * Load cached sql query + */ + function sql_load($query) + { + // Remove extra spaces and tabs + $query = preg_replace('/[\n\r\s\t]+/', ' ', $query); + + if (($rowset = $this->_read('sql_' . md5($query))) === false) + { + return false; + } + + $query_id = sizeof($this->sql_rowset); + $this->sql_rowset[$query_id] = $rowset; + $this->sql_row_pointer[$query_id] = 0; + + return $query_id; + } + + /** + * {@inheritDoc} + */ + function sql_save(phpbb_db_driver $db, $query, $query_result, $ttl) + { + // Remove extra spaces and tabs + $query = preg_replace('/[\n\r\s\t]+/', ' ', $query); + + $query_id = sizeof($this->sql_rowset); + $this->sql_rowset[$query_id] = array(); + $this->sql_row_pointer[$query_id] = 0; + + while ($row = $db->sql_fetchrow($query_result)) + { + $this->sql_rowset[$query_id][] = $row; + } + $db->sql_freeresult($query_result); + + if ($this->_write('sql_' . md5($query), $this->sql_rowset[$query_id], $ttl + time(), $query)) + { + return $query_id; + } + + return $query_result; + } + + /** + * Ceck if a given sql query exist in cache + */ + function sql_exists($query_id) + { + return isset($this->sql_rowset[$query_id]); + } + + /** + * Fetch row from cache (database) + */ + function sql_fetchrow($query_id) + { + if ($this->sql_row_pointer[$query_id] < sizeof($this->sql_rowset[$query_id])) + { + return $this->sql_rowset[$query_id][$this->sql_row_pointer[$query_id]++]; + } + + return false; + } + + /** + * Fetch a field from the current row of a cached database result (database) + */ + function sql_fetchfield($query_id, $field) + { + if ($this->sql_row_pointer[$query_id] < sizeof($this->sql_rowset[$query_id])) + { + return (isset($this->sql_rowset[$query_id][$this->sql_row_pointer[$query_id]][$field])) ? $this->sql_rowset[$query_id][$this->sql_row_pointer[$query_id]++][$field] : false; + } + + return false; + } + + /** + * Seek a specific row in an a cached database result (database) + */ + function sql_rowseek($rownum, $query_id) + { + if ($rownum >= sizeof($this->sql_rowset[$query_id])) + { + return false; + } + + $this->sql_row_pointer[$query_id] = $rownum; + return true; + } + + /** + * Free memory used for a cached database result (database) + */ + function sql_freeresult($query_id) + { + if (!isset($this->sql_rowset[$query_id])) + { + return false; + } + + unset($this->sql_rowset[$query_id]); + unset($this->sql_row_pointer[$query_id]); + + return true; + } + + /** + * Read cached data from a specified file + * + * @access private + * @param string $filename Filename to write + * @return mixed False if an error was encountered, otherwise the data type of the cached data + */ + function _read($filename) + { + global $phpEx; + + $file = "{$this->cache_dir}$filename.$phpEx"; + + $type = substr($filename, 0, strpos($filename, '_')); + + if (!file_exists($file)) + { + return false; + } + + if (!($handle = @fopen($file, 'rb'))) + { + return false; + } + + // Skip the PHP header + fgets($handle); + + if ($filename == 'data_global') + { + $this->vars = $this->var_expires = array(); + + $time = time(); + + while (($expires = (int) fgets($handle)) && !feof($handle)) + { + // Number of bytes of data + $bytes = substr(fgets($handle), 0, -1); + + if (!is_numeric($bytes) || ($bytes = (int) $bytes) === 0) + { + // We cannot process the file without a valid number of bytes + // so we discard it + fclose($handle); + + $this->vars = $this->var_expires = array(); + $this->is_modified = false; + + $this->remove_file($file); + + return false; + } + + if ($time >= $expires) + { + fseek($handle, $bytes, SEEK_CUR); + + continue; + } + + $var_name = substr(fgets($handle), 0, -1); + + // Read the length of bytes that consists of data. + $data = fread($handle, $bytes - strlen($var_name)); + $data = @unserialize($data); + + // Don't use the data if it was invalid + if ($data !== false) + { + $this->vars[$var_name] = $data; + $this->var_expires[$var_name] = $expires; + } + + // Absorb the LF + fgets($handle); + } + + fclose($handle); + + $this->is_modified = false; + + return true; + } + else + { + $data = false; + $line = 0; + + while (($buffer = fgets($handle)) && !feof($handle)) + { + $buffer = substr($buffer, 0, -1); // Remove the LF + + // $buffer is only used to read integers + // if it is non numeric we have an invalid + // cache file, which we will now remove. + if (!is_numeric($buffer)) + { + break; + } + + if ($line == 0) + { + $expires = (int) $buffer; + + if (time() >= $expires) + { + break; + } + + if ($type == 'sql') + { + // Skip the query + fgets($handle); + } + } + else if ($line == 1) + { + $bytes = (int) $buffer; + + // Never should have 0 bytes + if (!$bytes) + { + break; + } + + // Grab the serialized data + $data = fread($handle, $bytes); + + // Read 1 byte, to trigger EOF + fread($handle, 1); + + if (!feof($handle)) + { + // Somebody tampered with our data + $data = false; + } + break; + } + else + { + // Something went wrong + break; + } + $line++; + } + fclose($handle); + + // unserialize if we got some data + $data = ($data !== false) ? @unserialize($data) : $data; + + if ($data === false) + { + $this->remove_file($file); + return false; + } + + return $data; + } + } + + /** + * Write cache data to a specified file + * + * 'data_global' is a special case and the generated format is different for this file: + * + * + * (expiration) + * (length of var and serialised data) + * (var) + * (serialised data) + * ... (repeat) + * + * + * The other files have a similar format: + * + * + * (expiration) + * (query) [SQL files only] + * (length of serialised data) + * (serialised data) + * + * + * @access private + * @param string $filename Filename to write + * @param mixed $data Data to store + * @param int $expires Timestamp when the data expires + * @param string $query Query when caching SQL queries + * @return bool True if the file was successfully created, otherwise false + */ + function _write($filename, $data = null, $expires = 0, $query = '') + { + global $phpEx; + + $file = "{$this->cache_dir}$filename.$phpEx"; + + $lock = new phpbb_lock_flock($file); + $lock->acquire(); + + if ($handle = @fopen($file, 'wb')) + { + // File header + fwrite($handle, '<' . '?php exit; ?' . '>'); + + if ($filename == 'data_global') + { + // Global data is a different format + foreach ($this->vars as $var => $data) + { + if (strpos($var, "\r") !== false || strpos($var, "\n") !== false) + { + // CR/LF would cause fgets() to read the cache file incorrectly + // do not cache test entries, they probably won't be read back + // the cache keys should really be alphanumeric with a few symbols. + continue; + } + $data = serialize($data); + + // Write out the expiration time + fwrite($handle, "\n" . $this->var_expires[$var] . "\n"); + + // Length of the remaining data for this var (ignoring two LF's) + fwrite($handle, strlen($data . $var) . "\n"); + fwrite($handle, $var . "\n"); + fwrite($handle, $data); + } + } + else + { + fwrite($handle, "\n" . $expires . "\n"); + + if (strpos($filename, 'sql_') === 0) + { + fwrite($handle, $query . "\n"); + } + $data = serialize($data); + + fwrite($handle, strlen($data) . "\n"); + fwrite($handle, $data); + } + + fclose($handle); + + if (!function_exists('phpbb_chmod')) + { + global $phpbb_root_path; + include($phpbb_root_path . 'includes/functions.' . $phpEx); + } + + phpbb_chmod($file, CHMOD_READ | CHMOD_WRITE); + + $return_value = true; + } + else + { + $return_value = false; + } + + $lock->release(); + + return $return_value; + } + + /** + * Removes/unlinks file + */ + function remove_file($filename, $check = false) + { + if (!function_exists('phpbb_is_writable')) + { + global $phpbb_root_path, $phpEx; + include($phpbb_root_path . 'includes/functions.' . $phpEx); + } + + if ($check && !phpbb_is_writable($this->cache_dir)) + { + // E_USER_ERROR - not using language entry - intended. + trigger_error('Unable to remove files within ' . $this->cache_dir . '. Please check directory permissions.', E_USER_ERROR); + } + + return @unlink($filename); + } +} diff --git a/phpBB/phpbb/cache/driver/interface.php b/phpBB/phpbb/cache/driver/interface.php new file mode 100644 index 0000000000..53f684d1c8 --- /dev/null +++ b/phpBB/phpbb/cache/driver/interface.php @@ -0,0 +1,144 @@ +memcache = new Memcache; + foreach(explode(',', PHPBB_ACM_MEMCACHE) as $u) + { + $parts = explode('/', $u); + $this->memcache->addServer(trim($parts[0]), trim($parts[1])); + } + $this->flags = (PHPBB_ACM_MEMCACHE_COMPRESS) ? MEMCACHE_COMPRESSED : 0; + } + + /** + * Unload the cache resources + * + * @return null + */ + function unload() + { + parent::unload(); + + $this->memcache->close(); + } + + /** + * Purge cache data + * + * @return null + */ + function purge() + { + $this->memcache->flush(); + + parent::purge(); + } + + /** + * Fetch an item from the cache + * + * @access protected + * @param string $var Cache key + * @return mixed Cached data + */ + function _read($var) + { + return $this->memcache->get($this->key_prefix . $var); + } + + /** + * Store data in the cache + * + * @access protected + * @param string $var Cache key + * @param mixed $data Data to store + * @param int $ttl Time-to-live of cached data + * @return bool True if the operation succeeded + */ + function _write($var, $data, $ttl = 2592000) + { + if (!$this->memcache->replace($this->key_prefix . $var, $data, $this->flags, $ttl)) + { + return $this->memcache->set($this->key_prefix . $var, $data, $this->flags, $ttl); + } + return true; + } + + /** + * Remove an item from the cache + * + * @access protected + * @param string $var Cache key + * @return bool True if the operation succeeded + */ + function _delete($var) + { + return $this->memcache->delete($this->key_prefix . $var); + } +} diff --git a/phpBB/phpbb/cache/driver/memory.php b/phpBB/phpbb/cache/driver/memory.php new file mode 100644 index 0000000000..f77a1df316 --- /dev/null +++ b/phpBB/phpbb/cache/driver/memory.php @@ -0,0 +1,439 @@ +cache_dir = $phpbb_root_path . 'cache/'; + $this->key_prefix = substr(md5($dbname . $table_prefix), 0, 8) . '_'; + + if (!isset($this->extension) || !extension_loaded($this->extension)) + { + global $acm_type; + + trigger_error("Could not find required extension [{$this->extension}] for the ACM module $acm_type.", E_USER_ERROR); + } + + if (isset($this->function) && !function_exists($this->function)) + { + global $acm_type; + + trigger_error("The required function [{$this->function}] is not available for the ACM module $acm_type.", E_USER_ERROR); + } + } + + /** + * Load global cache + */ + function load() + { + // grab the global cache + $this->vars = $this->_read('global'); + + if ($this->vars !== false) + { + return true; + } + + return false; + } + + /** + * Unload cache object + */ + function unload() + { + $this->save(); + unset($this->vars); + unset($this->sql_rowset); + unset($this->sql_row_pointer); + + $this->vars = array(); + $this->sql_rowset = array(); + $this->sql_row_pointer = array(); + } + + /** + * Save modified objects + */ + function save() + { + if (!$this->is_modified) + { + return; + } + + $this->_write('global', $this->vars, 2592000); + + $this->is_modified = false; + } + + /** + * Tidy cache + */ + function tidy() + { + // cache has auto GC, no need to have any code here :) + + set_config('cache_last_gc', time(), true); + } + + /** + * Get saved cache object + */ + function get($var_name) + { + if ($var_name[0] == '_') + { + if (!$this->_exists($var_name)) + { + return false; + } + + return $this->_read($var_name); + } + else + { + return ($this->_exists($var_name)) ? $this->vars[$var_name] : false; + } + } + + /** + * Put data into cache + */ + function put($var_name, $var, $ttl = 2592000) + { + if ($var_name[0] == '_') + { + $this->_write($var_name, $var, $ttl); + } + else + { + $this->vars[$var_name] = $var; + $this->is_modified = true; + } + } + + /** + * Purge cache data + */ + function purge() + { + // Purge all phpbb cache files + $dir = @opendir($this->cache_dir); + + if (!$dir) + { + return; + } + + while (($entry = readdir($dir)) !== false) + { + if (strpos($entry, 'container_') !== 0 && + strpos($entry, 'url_matcher') !== 0 && + strpos($entry, 'sql_') !== 0 && + strpos($entry, 'data_') !== 0 && + strpos($entry, 'ctpl_') !== 0 && + strpos($entry, 'tpl_') !== 0) + { + continue; + } + + $this->remove_file($this->cache_dir . $entry); + } + closedir($dir); + + unset($this->vars); + unset($this->sql_rowset); + unset($this->sql_row_pointer); + + $this->vars = array(); + $this->sql_rowset = array(); + $this->sql_row_pointer = array(); + + $this->is_modified = false; + } + + + /** + * Destroy cache data + */ + function destroy($var_name, $table = '') + { + if ($var_name == 'sql' && !empty($table)) + { + if (!is_array($table)) + { + $table = array($table); + } + + foreach ($table as $table_name) + { + // gives us the md5s that we want + $temp = $this->_read('sql_' . $table_name); + + if ($temp === false) + { + continue; + } + + // delete each query ref + foreach ($temp as $md5_id => $void) + { + $this->_delete('sql_' . $md5_id); + } + + // delete the table ref + $this->_delete('sql_' . $table_name); + } + + return; + } + + if (!$this->_exists($var_name)) + { + return; + } + + if ($var_name[0] == '_') + { + $this->_delete($var_name); + } + else if (isset($this->vars[$var_name])) + { + $this->is_modified = true; + unset($this->vars[$var_name]); + + // We save here to let the following cache hits succeed + $this->save(); + } + } + + /** + * Check if a given cache entry exist + */ + function _exists($var_name) + { + if ($var_name[0] == '_') + { + return $this->_isset($var_name); + } + else + { + if (!sizeof($this->vars)) + { + $this->load(); + } + + return isset($this->vars[$var_name]); + } + } + + /** + * Load cached sql query + */ + function sql_load($query) + { + // Remove extra spaces and tabs + $query = preg_replace('/[\n\r\s\t]+/', ' ', $query); + $query_id = sizeof($this->sql_rowset); + + if (($result = $this->_read('sql_' . md5($query))) === false) + { + return false; + } + + $this->sql_rowset[$query_id] = $result; + $this->sql_row_pointer[$query_id] = 0; + + return $query_id; + } + + /** + * {@inheritDoc} + */ + function sql_save(phpbb_db_driver $db, $query, $query_result, $ttl) + { + // Remove extra spaces and tabs + $query = preg_replace('/[\n\r\s\t]+/', ' ', $query); + $hash = md5($query); + + // determine which tables this query belongs to + // Some queries use backticks, namely the get_database_size() query + // don't check for conformity, the SQL would error and not reach here. + if (!preg_match('/FROM \\(?(`?\\w+`?(?: \\w+)?(?:, ?`?\\w+`?(?: \\w+)?)*)\\)?/', $query, $regs)) + { + // Bail out if the match fails. + return $query_result; + } + $tables = array_map('trim', explode(',', $regs[1])); + + foreach ($tables as $table_name) + { + // Remove backticks + $table_name = ($table_name[0] == '`') ? substr($table_name, 1, -1) : $table_name; + + if (($pos = strpos($table_name, ' ')) !== false) + { + $table_name = substr($table_name, 0, $pos); + } + + $temp = $this->_read('sql_' . $table_name); + + if ($temp === false) + { + $temp = array(); + } + + $temp[$hash] = true; + + // This must never expire + $this->_write('sql_' . $table_name, $temp, 0); + } + + // store them in the right place + $query_id = sizeof($this->sql_rowset); + $this->sql_rowset[$query_id] = array(); + $this->sql_row_pointer[$query_id] = 0; + + while ($row = $db->sql_fetchrow($query_result)) + { + $this->sql_rowset[$query_id][] = $row; + } + $db->sql_freeresult($query_result); + + $this->_write('sql_' . $hash, $this->sql_rowset[$query_id], $ttl); + + return $query_id; + } + + /** + * Ceck if a given sql query exist in cache + */ + function sql_exists($query_id) + { + return isset($this->sql_rowset[$query_id]); + } + + /** + * Fetch row from cache (database) + */ + function sql_fetchrow($query_id) + { + if ($this->sql_row_pointer[$query_id] < sizeof($this->sql_rowset[$query_id])) + { + return $this->sql_rowset[$query_id][$this->sql_row_pointer[$query_id]++]; + } + + return false; + } + + /** + * Fetch a field from the current row of a cached database result (database) + */ + function sql_fetchfield($query_id, $field) + { + if ($this->sql_row_pointer[$query_id] < sizeof($this->sql_rowset[$query_id])) + { + return (isset($this->sql_rowset[$query_id][$this->sql_row_pointer[$query_id]][$field])) ? $this->sql_rowset[$query_id][$this->sql_row_pointer[$query_id]++][$field] : false; + } + + return false; + } + + /** + * Seek a specific row in an a cached database result (database) + */ + function sql_rowseek($rownum, $query_id) + { + if ($rownum >= sizeof($this->sql_rowset[$query_id])) + { + return false; + } + + $this->sql_row_pointer[$query_id] = $rownum; + return true; + } + + /** + * Free memory used for a cached database result (database) + */ + function sql_freeresult($query_id) + { + if (!isset($this->sql_rowset[$query_id])) + { + return false; + } + + unset($this->sql_rowset[$query_id]); + unset($this->sql_row_pointer[$query_id]); + + return true; + } + + /** + * Removes/unlinks file + */ + function remove_file($filename, $check = false) + { + if (!function_exists('phpbb_is_writable')) + { + global $phpbb_root_path, $phpEx; + include($phpbb_root_path . 'includes/functions.' . $phpEx); + } + + if ($check && !phpbb_is_writable($this->cache_dir)) + { + // E_USER_ERROR - not using language entry - intended. + trigger_error('Unable to remove files within ' . $this->cache_dir . '. Please check directory permissions.', E_USER_ERROR); + } + + return @unlink($filename); + } + + /** + * Check if a cache var exists + * + * @access protected + * @param string $var Cache key + * @return bool True if it exists, otherwise false + */ + function _isset($var) + { + // Most caches don't need to check + return true; + } +} diff --git a/phpBB/phpbb/cache/driver/null.php b/phpBB/phpbb/cache/driver/null.php new file mode 100644 index 0000000000..2fadc27ba3 --- /dev/null +++ b/phpBB/phpbb/cache/driver/null.php @@ -0,0 +1,154 @@ +redis = new Redis(); + + $args = func_get_args(); + if (!empty($args)) + { + $ok = call_user_func_array(array($this->redis, 'connect'), $args); + } + else + { + $ok = $this->redis->connect(PHPBB_ACM_REDIS_HOST, PHPBB_ACM_REDIS_PORT); + } + + if (!$ok) + { + trigger_error('Could not connect to redis server'); + } + + if (defined('PHPBB_ACM_REDIS_PASSWORD')) + { + if (!$this->redis->auth(PHPBB_ACM_REDIS_PASSWORD)) + { + global $acm_type; + + trigger_error("Incorrect password for the ACM module $acm_type.", E_USER_ERROR); + } + } + + $this->redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP); + $this->redis->setOption(Redis::OPT_PREFIX, $this->key_prefix); + + if (defined('PHPBB_ACM_REDIS_DB')) + { + if (!$this->redis->select(PHPBB_ACM_REDIS_DB)) + { + global $acm_type; + + trigger_error("Incorrect database for the ACM module $acm_type.", E_USER_ERROR); + } + } + } + + /** + * Unload the cache resources + * + * @return null + */ + function unload() + { + parent::unload(); + + $this->redis->close(); + } + + /** + * Purge cache data + * + * @return null + */ + function purge() + { + $this->redis->flushDB(); + + parent::purge(); + } + + /** + * Fetch an item from the cache + * + * @access protected + * @param string $var Cache key + * @return mixed Cached data + */ + function _read($var) + { + return $this->redis->get($var); + } + + /** + * Store data in the cache + * + * @access protected + * @param string $var Cache key + * @param mixed $data Data to store + * @param int $ttl Time-to-live of cached data + * @return bool True if the operation succeeded + */ + function _write($var, $data, $ttl = 2592000) + { + return $this->redis->setex($var, $ttl, $data); + } + + /** + * Remove an item from the cache + * + * @access protected + * @param string $var Cache key + * @return bool True if the operation succeeded + */ + function _delete($var) + { + if ($this->redis->delete($var) > 0) + { + return true; + } + return false; + } +} + diff --git a/phpBB/phpbb/cache/driver/wincache.php b/phpBB/phpbb/cache/driver/wincache.php new file mode 100644 index 0000000000..58f3b4a581 --- /dev/null +++ b/phpBB/phpbb/cache/driver/wincache.php @@ -0,0 +1,78 @@ +key_prefix . $var, $success); + + return ($success) ? $result : false; + } + + /** + * Store data in the cache + * + * @access protected + * @param string $var Cache key + * @param mixed $data Data to store + * @param int $ttl Time-to-live of cached data + * @return bool True if the operation succeeded + */ + function _write($var, $data, $ttl = 2592000) + { + return wincache_ucache_set($this->key_prefix . $var, $data, $ttl); + } + + /** + * Remove an item from the cache + * + * @access protected + * @param string $var Cache key + * @return bool True if the operation succeeded + */ + function _delete($var) + { + return wincache_ucache_delete($this->key_prefix . $var); + } +} diff --git a/phpBB/phpbb/cache/driver/xcache.php b/phpBB/phpbb/cache/driver/xcache.php new file mode 100644 index 0000000000..06c5fafd97 --- /dev/null +++ b/phpBB/phpbb/cache/driver/xcache.php @@ -0,0 +1,112 @@ + 0 +* - xcache.admin.enable_auth = off (or xcache.admin.user and xcache.admin.password set) +* +*/ +class phpbb_cache_driver_xcache extends phpbb_cache_driver_memory +{ + var $extension = 'XCache'; + + function __construct() + { + parent::__construct(); + + if (!function_exists('ini_get') || (int) ini_get('xcache.var_size') <= 0) + { + trigger_error('Increase xcache.var_size setting above 0 or enable ini_get() to use this ACM module.', E_USER_ERROR); + } + } + + /** + * Purge cache data + * + * @return null + */ + function purge() + { + // Run before for XCache, if admin functions are disabled it will terminate execution + parent::purge(); + + // If the admin authentication is enabled but not set up, this will cause a nasty error. + // Not much we can do about it though. + $n = xcache_count(XC_TYPE_VAR); + + for ($i = 0; $i < $n; $i++) + { + xcache_clear_cache(XC_TYPE_VAR, $i); + } + } + + /** + * Fetch an item from the cache + * + * @access protected + * @param string $var Cache key + * @return mixed Cached data + */ + function _read($var) + { + $result = xcache_get($this->key_prefix . $var); + + return ($result !== null) ? $result : false; + } + + /** + * Store data in the cache + * + * @access protected + * @param string $var Cache key + * @param mixed $data Data to store + * @param int $ttl Time-to-live of cached data + * @return bool True if the operation succeeded + */ + function _write($var, $data, $ttl = 2592000) + { + return xcache_set($this->key_prefix . $var, $data, $ttl); + } + + /** + * Remove an item from the cache + * + * @access protected + * @param string $var Cache key + * @return bool True if the operation succeeded + */ + function _delete($var) + { + return xcache_unset($this->key_prefix . $var); + } + + /** + * Check if a cache var exists + * + * @access protected + * @param string $var Cache key + * @return bool True if it exists, otherwise false + */ + function _isset($var) + { + return xcache_isset($this->key_prefix . $var); + } +} diff --git a/phpBB/phpbb/cache/service.php b/phpBB/phpbb/cache/service.php new file mode 100644 index 0000000000..69c5e0fdd0 --- /dev/null +++ b/phpBB/phpbb/cache/service.php @@ -0,0 +1,406 @@ +set_driver($driver); + $this->config = $config; + $this->db = $db; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + } + + /** + * Returns the cache driver used by this cache service. + * + * @return phpbb_cache_driver_interface The cache driver + */ + public function get_driver() + { + return $this->driver; + } + + /** + * Replaces the cache driver used by this cache service. + * + * @param phpbb_cache_driver_interface $driver The cache driver + */ + public function set_driver(phpbb_cache_driver_interface $driver) + { + $this->driver = $driver; + } + + public function __call($method, $arguments) + { + return call_user_func_array(array($this->driver, $method), $arguments); + } + + /** + * Obtain list of naughty words and build preg style replacement arrays for use by the + * calling script + */ + function obtain_word_list() + { + if (($censors = $this->driver->get('_word_censors')) === false) + { + $sql = 'SELECT word, replacement + FROM ' . WORDS_TABLE; + $result = $this->db->sql_query($sql); + + $censors = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $censors['match'][] = get_censor_preg_expression($row['word']); + $censors['replace'][] = $row['replacement']; + } + $this->db->sql_freeresult($result); + + $this->driver->put('_word_censors', $censors); + } + + return $censors; + } + + /** + * Obtain currently listed icons + */ + function obtain_icons() + { + if (($icons = $this->driver->get('_icons')) === false) + { + // Topic icons + $sql = 'SELECT * + FROM ' . ICONS_TABLE . ' + ORDER BY icons_order'; + $result = $this->db->sql_query($sql); + + $icons = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $icons[$row['icons_id']]['img'] = $row['icons_url']; + $icons[$row['icons_id']]['width'] = (int) $row['icons_width']; + $icons[$row['icons_id']]['height'] = (int) $row['icons_height']; + $icons[$row['icons_id']]['display'] = (bool) $row['display_on_posting']; + } + $this->db->sql_freeresult($result); + + $this->driver->put('_icons', $icons); + } + + return $icons; + } + + /** + * Obtain ranks + */ + function obtain_ranks() + { + if (($ranks = $this->driver->get('_ranks')) === false) + { + $sql = 'SELECT * + FROM ' . RANKS_TABLE . ' + ORDER BY rank_min DESC'; + $result = $this->db->sql_query($sql); + + $ranks = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + if ($row['rank_special']) + { + $ranks['special'][$row['rank_id']] = array( + 'rank_title' => $row['rank_title'], + 'rank_image' => $row['rank_image'] + ); + } + else + { + $ranks['normal'][] = array( + 'rank_title' => $row['rank_title'], + 'rank_min' => $row['rank_min'], + 'rank_image' => $row['rank_image'] + ); + } + } + $this->db->sql_freeresult($result); + + $this->driver->put('_ranks', $ranks); + } + + return $ranks; + } + + /** + * Obtain allowed extensions + * + * @param mixed $forum_id If false then check for private messaging, if int then check for forum id. If true, then only return extension informations. + * + * @return array allowed extensions array. + */ + function obtain_attach_extensions($forum_id) + { + if (($extensions = $this->driver->get('_extensions')) === false) + { + $extensions = array( + '_allowed_post' => array(), + '_allowed_pm' => array(), + ); + + // The rule is to only allow those extensions defined. ;) + $sql = 'SELECT e.extension, g.* + FROM ' . EXTENSIONS_TABLE . ' e, ' . EXTENSION_GROUPS_TABLE . ' g + WHERE e.group_id = g.group_id + AND (g.allow_group = 1 OR g.allow_in_pm = 1)'; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $extension = strtolower(trim($row['extension'])); + + $extensions[$extension] = array( + 'display_cat' => (int) $row['cat_id'], + 'download_mode' => (int) $row['download_mode'], + 'upload_icon' => trim($row['upload_icon']), + 'max_filesize' => (int) $row['max_filesize'], + 'allow_group' => $row['allow_group'], + 'allow_in_pm' => $row['allow_in_pm'], + 'group_name' => $row['group_name'], + ); + + $allowed_forums = ($row['allowed_forums']) ? unserialize(trim($row['allowed_forums'])) : array(); + + // Store allowed extensions forum wise + if ($row['allow_group']) + { + $extensions['_allowed_post'][$extension] = (!sizeof($allowed_forums)) ? 0 : $allowed_forums; + } + + if ($row['allow_in_pm']) + { + $extensions['_allowed_pm'][$extension] = 0; + } + } + $this->db->sql_freeresult($result); + + $this->driver->put('_extensions', $extensions); + } + + // Forum post + if ($forum_id === false) + { + // We are checking for private messages, therefore we only need to get the pm extensions... + $return = array('_allowed_' => array()); + + foreach ($extensions['_allowed_pm'] as $extension => $check) + { + $return['_allowed_'][$extension] = 0; + $return[$extension] = $extensions[$extension]; + } + + $extensions = $return; + } + else if ($forum_id === true) + { + return $extensions; + } + else + { + $forum_id = (int) $forum_id; + $return = array('_allowed_' => array()); + + foreach ($extensions['_allowed_post'] as $extension => $check) + { + // Check for allowed forums + if (is_array($check)) + { + $allowed = (!in_array($forum_id, $check)) ? false : true; + } + else + { + $allowed = true; + } + + if ($allowed) + { + $return['_allowed_'][$extension] = 0; + $return[$extension] = $extensions[$extension]; + } + } + + $extensions = $return; + } + + if (!isset($extensions['_allowed_'])) + { + $extensions['_allowed_'] = array(); + } + + return $extensions; + } + + /** + * Obtain active bots + */ + function obtain_bots() + { + if (($bots = $this->driver->get('_bots')) === false) + { + switch ($this->db->sql_layer) + { + case 'mssql': + case 'mssql_odbc': + case 'mssqlnative': + $sql = 'SELECT user_id, bot_agent, bot_ip + FROM ' . BOTS_TABLE . ' + WHERE bot_active = 1 + ORDER BY LEN(bot_agent) DESC'; + break; + + case 'firebird': + $sql = 'SELECT user_id, bot_agent, bot_ip + FROM ' . BOTS_TABLE . ' + WHERE bot_active = 1 + ORDER BY CHAR_LENGTH(bot_agent) DESC'; + break; + + // LENGTH supported by MySQL, IBM DB2 and Oracle for sure... + default: + $sql = 'SELECT user_id, bot_agent, bot_ip + FROM ' . BOTS_TABLE . ' + WHERE bot_active = 1 + ORDER BY LENGTH(bot_agent) DESC'; + break; + } + $result = $this->db->sql_query($sql); + + $bots = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $bots[] = $row; + } + $this->db->sql_freeresult($result); + + $this->driver->put('_bots', $bots); + } + + return $bots; + } + + /** + * Obtain cfg file data + */ + function obtain_cfg_items($style) + { + $parsed_array = $this->driver->get('_cfg_' . $style['style_path']); + + if ($parsed_array === false) + { + $parsed_array = array(); + } + + $filename = $this->phpbb_root_path . 'styles/' . $style['style_path'] . '/style.cfg'; + + if (!file_exists($filename)) + { + return $parsed_array; + } + + if (!isset($parsed_array['filetime']) || (($this->config['load_tplcompile'] && @filemtime($filename) > $parsed_array['filetime']))) + { + // Re-parse cfg file + $parsed_array = parse_cfg_file($filename); + $parsed_array['filetime'] = @filemtime($filename); + + $this->driver->put('_cfg_' . $style['style_path'], $parsed_array); + } + + return $parsed_array; + } + + /** + * Obtain disallowed usernames + */ + function obtain_disallowed_usernames() + { + if (($usernames = $this->driver->get('_disallowed_usernames')) === false) + { + $sql = 'SELECT disallow_username + FROM ' . DISALLOW_TABLE; + $result = $this->db->sql_query($sql); + + $usernames = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $usernames[] = str_replace('%', '.*?', preg_quote(utf8_clean_string($row['disallow_username']), '#')); + } + $this->db->sql_freeresult($result); + + $this->driver->put('_disallowed_usernames', $usernames); + } + + return $usernames; + } +} diff --git a/phpBB/phpbb/class_loader.php b/phpBB/phpbb/class_loader.php new file mode 100644 index 0000000000..02a2d584dc --- /dev/null +++ b/phpBB/phpbb/class_loader.php @@ -0,0 +1,170 @@ +path. + * This map is stored in cache and looked up if the cache is available. + * + * @var array + */ + private $cached_paths = array(); + + /** + * Creates a new phpbb_class_loader, which loads files with the given + * file extension from the given path. + * + * @param string $prefix Required class name prefix for files to be loaded + * @param string $path Directory to load files from + * @param string $php_ext The file extension for PHP files + * @param phpbb_cache_driver_interface $cache An implementation of the phpBB cache interface. + */ + public function __construct($prefix, $path, $php_ext = 'php', phpbb_cache_driver_interface $cache = null) + { + $this->prefix = $prefix; + $this->path = $path; + $this->php_ext = $php_ext; + + $this->set_cache($cache); + } + + /** + * Provide the class loader with a cache to store paths. If set to null, the + * the class loader will resolve paths by checking for the existance of every + * directory in the class name every time. + * + * @param phpbb_cache_driver_interface $cache An implementation of the phpBB cache interface. + */ + public function set_cache(phpbb_cache_driver_interface $cache = null) + { + if ($cache) + { + $this->cached_paths = $cache->get('class_loader_' . $this->prefix); + + if ($this->cached_paths === false) + { + $this->cached_paths = array(); + } + } + + $this->cache = $cache; + } + + /** + * Registers the class loader as an autoloader using SPL. + */ + public function register() + { + spl_autoload_register(array($this, 'load_class')); + } + + /** + * Removes the class loader from the SPL autoloader stack. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'load_class')); + } + + /** + * Resolves a phpBB class name to a relative path which can be included. + * + * @param string $class The class name to resolve, must have a phpbb_ + * prefix + * @return string|bool A relative path to the file containing the + * class or false if looking it up failed. + */ + public function resolve_path($class) + { + if (isset($this->cached_paths[$class])) + { + return $this->path . $this->cached_paths[$class] . '.' . $this->php_ext; + } + + if (!preg_match('/^' . $this->prefix . '[a-zA-Z0-9_]+$/', $class)) + { + return false; + } + + $parts = explode('_', substr($class, strlen($this->prefix))); + + $dirs = ''; + + for ($i = 0, $n = sizeof($parts); $i < $n && is_dir($this->path . $dirs . $parts[$i]); $i++) + { + $dirs .= $parts[$i] . '/'; + } + + // no file name left => use last dir name as file name + if ($i == sizeof($parts)) + { + $parts[] = $parts[$i - 1]; + } + + $relative_path = $dirs . implode(array_slice($parts, $i, sizeof($parts) - $i), '_'); + + if (!file_exists($this->path . $relative_path . '.' . $this->php_ext)) + { + return false; + } + + if ($this->cache) + { + $this->cached_paths[$class] = $relative_path; + $this->cache->put('class_loader_' . $this->prefix, $this->cached_paths); + } + + return $this->path . $relative_path . '.' . $this->php_ext; + } + + /** + * Resolves a class name to a path and then includes it. + * + * @param string $class The class name which is being loaded. + */ + public function load_class($class) + { + if (substr($class, 0, strlen($this->prefix)) === $this->prefix) + { + $path = $this->resolve_path($class); + + if ($path) + { + require $path; + } + } + } +} diff --git a/phpBB/phpbb/config/config.php b/phpBB/phpbb/config/config.php new file mode 100644 index 0000000000..4b533dd55c --- /dev/null +++ b/phpBB/phpbb/config/config.php @@ -0,0 +1,170 @@ + string) + */ + protected $config; + + /** + * Creates a configuration container with a default set of values + * + * @param array(string => string) $config The configuration data. + */ + public function __construct(array $config) + { + $this->config = $config; + } + + /** + * Retrieves an ArrayIterator over the configuration values. + * + * @return ArrayIterator An iterator over all config data + */ + public function getIterator() + { + return new ArrayIterator($this->config); + } + + /** + * Checks if the specified config value exists. + * + * @param string $key The configuration option's name. + * @return bool Whether the configuration option exists. + */ + public function offsetExists($key) + { + return isset($this->config[$key]); + } + + /** + * Retrieves a configuration value. + * + * @param string $key The configuration option's name. + * @return string The configuration value + */ + public function offsetGet($key) + { + return (isset($this->config[$key])) ? $this->config[$key] : ''; + } + + /** + * Temporarily overwrites the value of a configuration variable. + * + * The configuration change will not persist. It will be lost + * after the request. + * + * @param string $key The configuration option's name. + * @param string $value The temporary value. + */ + public function offsetSet($key, $value) + { + $this->config[$key] = $value; + } + + /** + * Called when deleting a configuration value directly, triggers an error. + * + * @param string $key The configuration option's name. + */ + public function offsetUnset($key) + { + trigger_error('Config values have to be deleted explicitly with the phpbb_config::delete($key) method.', E_USER_ERROR); + } + + /** + * Retrieves the number of configuration options currently set. + * + * @return int Number of config options + */ + public function count() + { + return count($this->config); + } + + /** + * Removes a configuration option + * + * @param String $key The configuration option's name + * @param bool $use_cache Whether this variable should be cached or if it + * changes too frequently to be efficiently cached + * @return null + */ + public function delete($key, $use_cache = true) + { + unset($this->config[$key]); + } + + /** + * Sets a configuration option's value + * + * @param string $key The configuration option's name + * @param string $value New configuration value + * @param bool $use_cache Whether this variable should be cached or if it + * changes too frequently to be efficiently cached. + */ + public function set($key, $value, $use_cache = true) + { + $this->config[$key] = $value; + } + + /** + * Sets a configuration option's value only if the old_value matches the + * current configuration value or the configuration value does not exist yet. + * + * @param string $key The configuration option's name + * @param string $old_value Current configuration value + * @param string $new_value New configuration value + * @param bool $use_cache Whether this variable should be cached or if it + * changes too frequently to be efficiently cached. + * @return bool True if the value was changed, false otherwise. + */ + public function set_atomic($key, $old_value, $new_value, $use_cache = true) + { + if (!isset($this->config[$key]) || $this->config[$key] == $old_value) + { + $this->config[$key] = $new_value; + return true; + } + return false; + } + + /** + * Increments an integer configuration value. + * + * @param string $key The configuration option's name + * @param int $increment Amount to increment by + * @param bool $use_cache Whether this variable should be cached or if it + * changes too frequently to be efficiently cached. + */ + function increment($key, $increment, $use_cache = true) + { + if (!isset($this->config[$key])) + { + $this->config[$key] = 0; + } + + $this->config[$key] += $increment; + } +} diff --git a/phpBB/phpbb/config/db.php b/phpBB/phpbb/config/db.php new file mode 100644 index 0000000000..b18369a479 --- /dev/null +++ b/phpBB/phpbb/config/db.php @@ -0,0 +1,207 @@ +db = $db; + $this->cache = $cache; + $this->table = $table; + + if (($config = $cache->get('config')) !== false) + { + $sql = 'SELECT config_name, config_value + FROM ' . $this->table . ' + WHERE is_dynamic = 1'; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $config[$row['config_name']] = $row['config_value']; + } + $this->db->sql_freeresult($result); + } + else + { + $config = $cached_config = array(); + + $sql = 'SELECT config_name, config_value, is_dynamic + FROM ' . $this->table; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + if (!$row['is_dynamic']) + { + $cached_config[$row['config_name']] = $row['config_value']; + } + + $config[$row['config_name']] = $row['config_value']; + } + $this->db->sql_freeresult($result); + + $cache->put('config', $cached_config); + } + + parent::__construct($config); + } + + /** + * Removes a configuration option + * + * @param String $key The configuration option's name + * @param bool $use_cache Whether this variable should be cached or if it + * changes too frequently to be efficiently cached + * @return null + */ + public function delete($key, $use_cache = true) + { + $sql = 'DELETE FROM ' . $this->table . " + WHERE config_name = '" . $this->db->sql_escape($key) . "'"; + $this->db->sql_query($sql); + + unset($this->config[$key]); + + if ($use_cache) + { + $this->cache->destroy('config'); + } + } + + /** + * Sets a configuration option's value + * + * @param string $key The configuration option's name + * @param string $value New configuration value + * @param bool $use_cache Whether this variable should be cached or if it + * changes too frequently to be efficiently cached. + */ + public function set($key, $value, $use_cache = true) + { + $this->set_atomic($key, false, $value, $use_cache); + } + + /** + * Sets a configuration option's value only if the old_value matches the + * current configuration value or the configuration value does not exist yet. + * + * @param string $key The configuration option's name + * @param mixed $old_value Current configuration value or false to ignore + * the old value + * @param string $new_value New configuration value + * @param bool $use_cache Whether this variable should be cached or if it + * changes too frequently to be efficiently cached + * @return bool True if the value was changed, false otherwise + */ + public function set_atomic($key, $old_value, $new_value, $use_cache = true) + { + $sql = 'UPDATE ' . $this->table . " + SET config_value = '" . $this->db->sql_escape($new_value) . "' + WHERE config_name = '" . $this->db->sql_escape($key) . "'"; + + if ($old_value !== false) + { + $sql .= " AND config_value = '" . $this->db->sql_escape($old_value) . "'"; + } + + $result = $this->db->sql_query($sql); + + if (!$this->db->sql_affectedrows($result) && isset($this->config[$key])) + { + return false; + } + + if (!isset($this->config[$key])) + { + $sql = 'INSERT INTO ' . $this->table . ' ' . $this->db->sql_build_array('INSERT', array( + 'config_name' => $key, + 'config_value' => $new_value, + 'is_dynamic' => ($use_cache) ? 0 : 1)); + $this->db->sql_query($sql); + } + + if ($use_cache) + { + $this->cache->destroy('config'); + } + + $this->config[$key] = $new_value; + return true; + } + + /** + * Increments an integer config value directly in the database. + * + * Using this method instead of setting the new value directly avoids race + * conditions and unlike set_atomic it cannot fail. + * + * @param string $key The configuration option's name + * @param int $increment Amount to increment by + * @param bool $use_cache Whether this variable should be cached or if it + * changes too frequently to be efficiently cached. + */ + function increment($key, $increment, $use_cache = true) + { + if (!isset($this->config[$key])) + { + $this->set($key, '0', $use_cache); + } + + $sql_update = $this->db->cast_expr_to_string($this->db->cast_expr_to_bigint('config_value') . ' + ' . (int) $increment); + + $this->db->sql_query('UPDATE ' . $this->table . ' + SET config_value = ' . $sql_update . " + WHERE config_name = '" . $this->db->sql_escape($key) . "'"); + + if ($use_cache) + { + $this->cache->destroy('config'); + } + + $this->config[$key] += $increment; + } +} diff --git a/phpBB/phpbb/config/db_text.php b/phpBB/phpbb/config/db_text.php new file mode 100644 index 0000000000..b365cb5c77 --- /dev/null +++ b/phpBB/phpbb/config/db_text.php @@ -0,0 +1,163 @@ +db = $db; + $this->table = $this->db->sql_escape($table); + } + + /** + * Sets the configuration option with the name $key to $value. + * + * @param string $key The configuration option's name + * @param string $value New configuration value + * + * @return null + */ + public function set($key, $value) + { + $this->set_array(array($key => $value)); + } + + /** + * Gets the configuration value for the name $key. + * + * @param string $key The configuration option's name + * + * @return string|null String result on success + * null if there is no such option + */ + public function get($key) + { + $map = $this->get_array(array($key)); + + return isset($map[$key]) ? $map[$key] : null; + } + + /** + * Removes the configuration option with the name $key. + * + * @param string $key The configuration option's name + * + * @return null + */ + public function delete($key) + { + $this->delete_array(array($key)); + } + + /** + * Mass set configuration options: Receives an associative array, + * treats array keys as configuration option names and associated + * array values as their configuration option values. + * + * @param array $map Map from configuration names to values + * + * @return null + */ + public function set_array(array $map) + { + $this->db->sql_transaction('begin'); + + foreach ($map as $key => $value) + { + $sql = 'UPDATE ' . $this->table . " + SET config_value = '" . $this->db->sql_escape($value) . "' + WHERE config_name = '" . $this->db->sql_escape($key) . "'"; + $result = $this->db->sql_query($sql); + + if (!$this->db->sql_affectedrows($result)) + { + $sql = 'INSERT INTO ' . $this->table . ' ' . $this->db->sql_build_array('INSERT', array( + 'config_name' => $key, + 'config_value' => $value, + )); + $this->db->sql_query($sql); + } + } + + $this->db->sql_transaction('commit'); + } + + /** + * Mass get configuration options: Receives a set of configuration + * option names and returns the result as a key => value map where + * array keys are configuration option names and array values are + * associated config option values. + * + * @param array $keys Set of configuration option names + * + * @return array Map from configuration names to values + */ + public function get_array(array $keys) + { + $sql = 'SELECT * + FROM ' . $this->table . ' + WHERE ' . $this->db->sql_in_set('config_name', $keys, false, true); + $result = $this->db->sql_query($sql); + + $map = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $map[$row['config_name']] = $row['config_value']; + } + $this->db->sql_freeresult($result); + + return $map; + } + + /** + * Mass delete configuration options. + * + * @param array $keys Set of configuration option names + * + * @return null + */ + public function delete_array(array $keys) + { + $sql = 'DELETE + FROM ' . $this->table . ' + WHERE ' . $this->db->sql_in_set('config_name', $keys, false, true); + $result = $this->db->sql_query($sql); + } +} diff --git a/phpBB/phpbb/content_visibility.php b/phpBB/phpbb/content_visibility.php new file mode 100644 index 0000000000..4ad5f6793e --- /dev/null +++ b/phpBB/phpbb/content_visibility.php @@ -0,0 +1,651 @@ +auth = $auth; + $this->db = $db; + $this->user = $user; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + $this->forums_table = $forums_table; + $this->posts_table = $posts_table; + $this->topics_table = $topics_table; + $this->users_table = $users_table; + } + + /** + * Can the current logged-in user soft-delete posts? + * + * @param $forum_id int Forum ID whose permissions to check + * @param $poster_id int Poster ID of the post in question + * @param $post_locked bool Is the post locked? + * @return bool + */ + public function can_soft_delete($forum_id, $poster_id, $post_locked) + { + if ($this->auth->acl_get('m_softdelete', $forum_id)) + { + return true; + } + else if ($this->auth->acl_get('f_softdelete', $forum_id) && $poster_id == $this->user->data['user_id'] && !$post_locked) + { + return true; + } + + return false; + } + + /** + * Get the topics post count or the forums post/topic count based on permissions + * + * @param $mode string One of topic_posts, forum_posts or forum_topics + * @param $data array Array with the topic/forum data to calculate from + * @param $forum_id int The forum id is used for permission checks + * @return int Number of posts/topics the user can see in the topic/forum + */ + public function get_count($mode, $data, $forum_id) + { + if (!$this->auth->acl_get('m_approve', $forum_id)) + { + return (int) $data[$mode . '_approved']; + } + + return (int) $data[$mode . '_approved'] + (int) $data[$mode . '_unapproved'] + (int) $data[$mode . '_softdeleted']; + } + + /** + * Create topic/post visibility SQL for a given forum ID + * + * Note: Read permissions are not checked. + * + * @param $mode string Either "topic" or "post" + * @param $forum_id int The forum id is used for permission checks + * @param $table_alias string Table alias to prefix in SQL queries + * @return string The appropriate combination SQL logic for topic/post_visibility + */ + public function get_visibility_sql($mode, $forum_id, $table_alias = '') + { + if ($this->auth->acl_get('m_approve', $forum_id)) + { + return '1 = 1'; + } + + return $table_alias . $mode . '_visibility = ' . ITEM_APPROVED; + } + + /** + * Create topic/post visibility SQL for a set of forums + * + * Note: Read permissions are not checked. Forums without read permissions + * should not be in $forum_ids + * + * @param $mode string Either "topic" or "post" + * @param $forum_ids array Array of forum ids which the posts/topics are limited to + * @param $table_alias string Table alias to prefix in SQL queries + * @return string The appropriate combination SQL logic for topic/post_visibility + */ + public function get_forums_visibility_sql($mode, $forum_ids = array(), $table_alias = '') + { + $where_sql = '('; + + $approve_forums = array_intersect($forum_ids, array_keys($this->auth->acl_getf('m_approve', true))); + + if (sizeof($approve_forums)) + { + // Remove moderator forums from the rest + $forum_ids = array_diff($forum_ids, $approve_forums); + + if (!sizeof($forum_ids)) + { + // The user can see all posts/topics in all specified forums + return $this->db->sql_in_set($table_alias . 'forum_id', $approve_forums); + } + else + { + // Moderator can view all posts/topics in some forums + $where_sql .= $this->db->sql_in_set($table_alias . 'forum_id', $approve_forums) . ' OR '; + } + } + else + { + // The user is just a normal user + return $table_alias . $mode . '_visibility = ' . ITEM_APPROVED . ' + AND ' . $this->db->sql_in_set($table_alias . 'forum_id', $forum_ids, false, true); + } + + $where_sql .= '(' . $table_alias . $mode . '_visibility = ' . ITEM_APPROVED . ' + AND ' . $this->db->sql_in_set($table_alias . 'forum_id', $forum_ids) . '))'; + + return $where_sql; + } + + /** + * Create topic/post visibility SQL for all forums on the board + * + * Note: Read permissions are not checked. Forums without read permissions + * should be in $exclude_forum_ids + * + * @param $mode string Either "topic" or "post" + * @param $exclude_forum_ids array Array of forum ids which are excluded + * @param $table_alias string Table alias to prefix in SQL queries + * @return string The appropriate combination SQL logic for topic/post_visibility + */ + public function get_global_visibility_sql($mode, $exclude_forum_ids = array(), $table_alias = '') + { + $where_sqls = array(); + + $approve_forums = array_diff(array_keys($this->auth->acl_getf('m_approve', true)), $exclude_forum_ids); + + if (sizeof($exclude_forum_ids)) + { + $where_sqls[] = '(' . $this->db->sql_in_set($table_alias . 'forum_id', $exclude_forum_ids, true) . ' + AND ' . $table_alias . $mode . '_visibility = ' . ITEM_APPROVED . ')'; + } + else + { + $where_sqls[] = $table_alias . $mode . '_visibility = ' . ITEM_APPROVED; + } + + if (sizeof($approve_forums)) + { + $where_sqls[] = $this->db->sql_in_set($table_alias . 'forum_id', $approve_forums); + return '(' . implode(' OR ', $where_sqls) . ')'; + } + + // There is only one element, so we just return that one + return $where_sqls[0]; + } + + /** + * Change visibility status of one post or all posts of a topic + * + * @param $visibility int Element of {ITEM_APPROVED, ITEM_DELETED} + * @param $post_id mixed Post ID or array of post IDs to act on, + * if it is empty, all posts of topic_id will be modified + * @param $topic_id int Topic where $post_id is found + * @param $forum_id int Forum where $topic_id is found + * @param $user_id int User performing the action + * @param $time int Timestamp when the action is performed + * @param $reason string Reason why the visibilty was changed. + * @param $is_starter bool Is this the first post of the topic changed? + * @param $is_latest bool Is this the last post of the topic changed? + * @param $limit_visibility mixed Limit updating per topic_id to a certain visibility + * @param $limit_delete_time mixed Limit updating per topic_id to a certain deletion time + * @return array Changed post data, empty array if an error occured. + */ + public function set_post_visibility($visibility, $post_id, $topic_id, $forum_id, $user_id, $time, $reason, $is_starter, $is_latest, $limit_visibility = false, $limit_delete_time = false) + { + if (!in_array($visibility, array(ITEM_APPROVED, ITEM_DELETED))) + { + return array(); + } + + if ($post_id) + { + if (is_array($post_id)) + { + $where_sql = $this->db->sql_in_set('post_id', array_map('intval', $post_id)); + } + else + { + $where_sql = 'post_id = ' . (int) $post_id; + } + $where_sql .= ' AND topic_id = ' . (int) $topic_id; + } + else + { + $where_sql = 'topic_id = ' . (int) $topic_id; + + // Limit the posts to a certain visibility and deletion time + // This allows us to only restore posts, that were approved + // when the topic got soft deleted. So previous soft deleted + // and unapproved posts are still soft deleted/unapproved + if ($limit_visibility !== false) + { + $where_sql .= ' AND post_visibility = ' . (int) $limit_visibility; + } + + if ($limit_delete_time !== false) + { + $where_sql .= ' AND post_delete_time = ' . (int) $limit_delete_time; + } + } + + $sql = 'SELECT poster_id, post_id, post_postcount, post_visibility + FROM ' . $this->posts_table . ' + WHERE ' . $where_sql; + $result = $this->db->sql_query($sql); + + $post_ids = $poster_postcounts = $postcounts = $postcount_visibility = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $post_ids[] = (int) $row['post_id']; + + if ($row['post_visibility'] != $visibility) + { + if ($row['post_postcount'] && !isset($poster_postcounts[(int) $row['poster_id']])) + { + $poster_postcounts[(int) $row['poster_id']] = 1; + } + else if ($row['post_postcount']) + { + $poster_postcounts[(int) $row['poster_id']]++; + } + + if (!isset($postcount_visibility[$row['post_visibility']])) + { + $postcount_visibility[$row['post_visibility']] = 1; + } + else + { + $postcount_visibility[$row['post_visibility']]++; + } + } + } + $this->db->sql_freeresult($result); + + if (empty($post_ids)) + { + return array(); + } + + $data = array( + 'post_visibility' => (int) $visibility, + 'post_delete_user' => (int) $user_id, + 'post_delete_time' => ((int) $time) ?: time(), + 'post_delete_reason' => truncate_string($reason, 255, 255, false), + ); + + $sql = 'UPDATE ' . $this->posts_table . ' + SET ' . $this->db->sql_build_array('UPDATE', $data) . ' + WHERE ' . $this->db->sql_in_set('post_id', $post_ids); + $this->db->sql_query($sql); + + // Group the authors by post count, to reduce the number of queries + foreach ($poster_postcounts as $poster_id => $num_posts) + { + $postcounts[$num_posts][] = $poster_id; + } + + // Update users postcounts + foreach ($postcounts as $num_posts => $poster_ids) + { + if ($visibility == ITEM_DELETED) + { + $sql = 'UPDATE ' . $this->users_table . ' + SET user_posts = 0 + WHERE ' . $this->db->sql_in_set('user_id', $poster_ids) . ' + AND user_posts < ' . $num_posts; + $this->db->sql_query($sql); + + $sql = 'UPDATE ' . $this->users_table . ' + SET user_posts = user_posts - ' . $num_posts . ' + WHERE ' . $this->db->sql_in_set('user_id', $poster_ids) . ' + AND user_posts >= ' . $num_posts; + $this->db->sql_query($sql); + } + else + { + $sql = 'UPDATE ' . $this->users_table . ' + SET user_posts = user_posts + ' . $num_posts . ' + WHERE ' . $this->db->sql_in_set('user_id', $poster_ids); + $this->db->sql_query($sql); + } + } + + $update_topic_postcount = true; + + // Sync the first/last topic information if needed + if (!$is_starter && $is_latest) + { + // update_post_information can only update the last post info ... + if ($topic_id) + { + update_post_information('topic', $topic_id, false); + } + if ($forum_id) + { + update_post_information('forum', $forum_id, false); + } + } + else if ($is_starter && $topic_id) + { + if (!function_exists('sync')) + { + include($this->phpbb_root_path . 'includes/functions_admin.' . $this->php_ext); + } + + // ... so we need to use sync, if the first post is changed. + // The forum is resynced recursive by sync() itself. + sync('topic', 'topic_id', $topic_id, true); + + // sync recalculates the topic replies and forum posts by itself, so we don't do that. + $update_topic_postcount = false; + } + + // Update the topic's reply count and the forum's post count + if ($update_topic_postcount) + { + $cur_posts = $cur_unapproved_posts = $cur_softdeleted_posts = 0; + foreach ($postcount_visibility as $post_visibility => $visibility_posts) + { + // We need to substract the posts from the counters ... + if ($post_visibility == ITEM_APPROVED) + { + $cur_posts += $visibility_posts; + } + else if ($post_visibility == ITEM_UNAPPROVED) + { + $cur_unapproved_posts += $visibility_posts; + } + else if ($post_visibility == ITEM_DELETED) + { + $cur_softdeleted_posts += $visibility_posts; + } + } + + $sql_ary = array(); + if ($visibility == ITEM_DELETED) + { + if ($cur_posts) + { + $sql_ary['posts_approved'] = ' - ' . $cur_posts; + } + if ($cur_unapproved_posts) + { + $sql_ary['posts_unapproved'] = ' - ' . $cur_unapproved_posts; + } + if ($cur_posts + $cur_unapproved_posts) + { + $sql_ary['posts_softdeleted'] = ' + ' . ($cur_posts + $cur_unapproved_posts); + } + } + else + { + if ($cur_unapproved_posts) + { + $sql_ary['posts_unapproved'] = ' - ' . $cur_unapproved_posts; + } + if ($cur_softdeleted_posts) + { + $sql_ary['posts_softdeleted'] = ' - ' . $cur_softdeleted_posts; + } + if ($cur_softdeleted_posts + $cur_unapproved_posts) + { + $sql_ary['posts_approved'] = ' + ' . ($cur_softdeleted_posts + $cur_unapproved_posts); + } + } + + if (sizeof($sql_ary)) + { + $topic_sql = $forum_sql = array(); + + foreach ($sql_ary as $field => $value_change) + { + $topic_sql[] = 'topic_' . $field . ' = topic_' . $field . $value_change; + $forum_sql[] = 'forum_' . $field . ' = forum_' . $field . $value_change; + } + + // Update the number for replies and posts + $sql = 'UPDATE ' . $this->topics_table . ' + SET ' . implode(', ', $topic_sql) . ' + WHERE topic_id = ' . (int) $topic_id; + $this->db->sql_query($sql); + + $sql = 'UPDATE ' . $this->forums_table . ' + SET ' . implode(', ', $forum_sql) . ' + WHERE forum_id = ' . (int) $forum_id; + $this->db->sql_query($sql); + } + } + + return $data; + } + + /** + * Set topic visibility + * + * Allows approving (which is akin to undeleting/restore) or soft deleting an entire topic. + * Calls set_post_visibility as needed. + * + * Note: By default, when a soft deleted topic is restored. Only posts that + * were approved at the time of soft deleting, are being restored. + * Same applies to soft deleting. Only approved posts will be marked + * as soft deleted. + * If you want to update all posts, use the force option. + * + * @param $visibility int Element of {ITEM_APPROVED, ITEM_DELETED} + * @param $topic_id mixed Topic ID to act on + * @param $forum_id int Forum where $topic_id is found + * @param $user_id int User performing the action + * @param $time int Timestamp when the action is performed + * @param $reason string Reason why the visibilty was changed. + * @param $force_update_all bool Force to update all posts within the topic + * @return array Changed topic data, empty array if an error occured. + */ + public function set_topic_visibility($visibility, $topic_id, $forum_id, $user_id, $time, $reason, $force_update_all = false) + { + if (!in_array($visibility, array(ITEM_APPROVED, ITEM_DELETED))) + { + return array(); + } + + if (!$force_update_all) + { + $sql = 'SELECT topic_visibility, topic_delete_time + FROM ' . $this->topics_table . ' + WHERE topic_id = ' . (int) $topic_id; + $result = $this->db->sql_query($sql); + $original_topic_data = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if (!$original_topic_data) + { + // The topic does not exist... + return array(); + } + } + + // Note, we do not set a reason for the posts, just for the topic + $data = array( + 'topic_visibility' => (int) $visibility, + 'topic_delete_user' => (int) $user_id, + 'topic_delete_time' => ((int) $time) ?: time(), + 'topic_delete_reason' => truncate_string($reason, 255, 255, false), + ); + + $sql = 'UPDATE ' . $this->topics_table . ' + SET ' . $this->db->sql_build_array('UPDATE', $data) . ' + WHERE topic_id = ' . (int) $topic_id; + $this->db->sql_query($sql); + + if (!$this->db->sql_affectedrows()) + { + return array(); + } + + if (!$force_update_all && $original_topic_data['topic_delete_time'] && $original_topic_data['topic_visibility'] == ITEM_DELETED && $visibility == ITEM_APPROVED) + { + // If we're restoring a topic we only restore posts, that were soft deleted through the topic soft deletion. + self::set_post_visibility($visibility, false, $topic_id, $forum_id, $user_id, $time, '', true, true, $original_topic_data['topic_visibility'], $original_topic_data['topic_delete_time']); + } + else if (!$force_update_all && $original_topic_data['topic_visibility'] == ITEM_APPROVED && $visibility == ITEM_DELETED) + { + // If we're soft deleting a topic we only approved posts are soft deleted. + self::set_post_visibility($visibility, false, $topic_id, $forum_id, $user_id, $time, '', true, true, $original_topic_data['topic_visibility']); + } + else + { + self::set_post_visibility($visibility, false, $topic_id, $forum_id, $user_id, $time, '', true, true); + } + + return $data; + } + + /** + * Add post to topic and forum statistics + * + * @param $data array Contains information from the topics table about given topic + * @param $sql_data array Populated with the SQL changes, may be empty at call time + * @return void + */ + public function add_post_to_statistic($data, &$sql_data) + { + $sql_data[$this->topics_table] = (($sql_data[$this->topics_table]) ? $sql_data[$this->topics_table] . ', ' : '') . 'topic_posts_approved = topic_posts_approved + 1'; + + $sql_data[$this->forums_table] = (($sql_data[$this->forums_table]) ? $sql_data[$this->forums_table] . ', ' : '') . 'forum_posts_approved = forum_posts_approved + 1'; + + if ($data['post_postcount']) + { + $sql_data[$this->users_table] = (($sql_data[$this->users_table]) ? $sql_data[$this->users_table] . ', ' : '') . 'user_posts = user_posts + 1'; + } + + set_config_count('num_posts', 1, true); + } + + /** + * Remove post from topic and forum statistics + * + * @param $data array Contains information from the topics table about given topic + * @param $sql_data array Populated with the SQL changes, may be empty at call time + * @return void + */ + public function remove_post_from_statistic($data, &$sql_data) + { + $sql_data[$this->topics_table] = ((!empty($sql_data[$this->topics_table])) ? $sql_data[$this->topics_table] . ', ' : '') . 'topic_posts_approved = topic_posts_approved - 1'; + $sql_data[$this->forums_table] = ((!empty($sql_data[$this->forums_table])) ? $sql_data[$this->forums_table] . ', ' : '') . 'forum_posts_approved = forum_posts_approved - 1'; + + if ($data['post_postcount']) + { + $sql_data[$this->users_table] = ((!empty($sql_data[$this->users_table])) ? $sql_data[$this->users_table] . ', ' : '') . 'user_posts = user_posts - 1'; + } + + set_config_count('num_posts', -1, true); + } + + /** + * Remove topic from forum statistics + * + * @param $topic_id int The topic to act on + * @param $forum_id int Forum where the topic is found + * @param $topic_row array Contains information from the topic, may be empty at call time + * @param $sql_data array Populated with the SQL changes, may be empty at call time + * @return void + */ + public function remove_topic_from_statistic($topic_id, $forum_id, &$topic_row, &$sql_data) + { + // Do we need to grab some topic informations? + if (!sizeof($topic_row)) + { + $sql = 'SELECT topic_type, topic_posts_approved, topic_posts_unapproved, topic_posts_softdeleted, topic_visibility + FROM ' . $this->topics_table . ' + WHERE topic_id = ' . (int) $topic_id; + $result = $this->db->sql_query($sql); + $topic_row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + } + + // If this is an edited topic or the first post the topic gets completely disapproved later on... + $sql_data[$this->forums_table] = (($sql_data[$this->forums_table]) ? $sql_data[$this->forums_table] . ', ' : '') . 'forum_topics_approved = forum_topics_approved - 1'; + $sql_data[$this->forums_table] .= ', forum_posts_approved = forum_posts_approved - ' . $topic_row['topic_posts_approved']; + $sql_data[$this->forums_table] .= ', forum_posts_unapproved = forum_posts_unapproved - ' . $topic_row['topic_posts_unapproved']; + $sql_data[$this->forums_table] .= ', forum_posts_softdeleted = forum_posts_softdeleted - ' . $topic_row['topic_posts_softdeleted']; + + set_config_count('num_topics', -1, true); + set_config_count('num_posts', $topic_row['topic_posts_approved'] * (-1), true); + + // Get user post count information + $sql = 'SELECT poster_id, COUNT(post_id) AS num_posts + FROM ' . $this->posts_table . ' + WHERE topic_id = ' . (int) $topic_id . ' + AND post_postcount = 1 + AND post_visibility = ' . ITEM_APPROVED . ' + GROUP BY poster_id'; + $result = $this->db->sql_query($sql); + + $postcounts = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $postcounts[(int) $row['num_posts']][] = (int) $row['poster_id']; + } + $this->db->sql_freeresult($result); + + // Decrement users post count + foreach ($postcounts as $num_posts => $poster_ids) + { + $sql = 'UPDATE ' . $this->users_table . ' + SET user_posts = 0 + WHERE user_posts < ' . $num_posts . ' + AND ' . $this->db->sql_in_set('user_id', $poster_ids); + $this->db->sql_query($sql); + + $sql = 'UPDATE ' . $this->users_table . ' + SET user_posts = user_posts - ' . $num_posts . ' + WHERE user_posts >= ' . $num_posts . ' + AND ' . $this->db->sql_in_set('user_id', $poster_ids); + $this->db->sql_query($sql); + } + } +} diff --git a/phpBB/phpbb/controller/exception.php b/phpBB/phpbb/controller/exception.php new file mode 100644 index 0000000000..faa8b6b584 --- /dev/null +++ b/phpBB/phpbb/controller/exception.php @@ -0,0 +1,24 @@ +template = $template; + $this->user = $user; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + } + + /** + * Automate setting up the page and creating the response object. + * + * @param string $handle The template handle to render + * @param string $page_title The title of the page to output + * @param int $status_code The status code to be sent to the page header + * @return Response object containing rendered page + */ + public function render($template_file, $page_title = '', $status_code = 200) + { + page_header($page_title); + + $this->template->set_filenames(array( + 'body' => $template_file, + )); + + page_footer(true, false, false); + + return new Response($this->template->assign_display('body'), $status_code); + } + + /** + * Generate a URL + * + * @param string $route The route to travel + * @param mixed $params String or array of additional url parameters + * @param bool $is_amp Is url using & (true) or & (false) + * @param string $session_id Possibility to use a custom session id instead of the global one + * @return string The URL already passed through append_sid() + */ + public function url($route, $params = false, $is_amp = true, $session_id = false) + { + $route_params = ''; + if (($route_delim = strpos($route, '?')) !== false) + { + $route_params = substr($route, $route_delim); + $route = substr($route, 0, $route_delim); + } + + if (is_array($params) && !empty($params)) + { + $params = array_merge(array( + 'controller' => $route, + ), $params); + } + else if (is_string($params) && $params) + { + $params = 'controller=' . $route . (($is_amp) ? '&' : '&') . $params; + } + else + { + $params = array('controller' => $route); + } + + return append_sid($this->phpbb_root_path . 'app.' . $this->php_ext . $route_params, $params, $is_amp, $session_id); + } + + /** + * Output an error, effectively the same thing as trigger_error + * + * @param string $message The error message + * @param string $code The error code (e.g. 404, 500, 503, etc.) + * @return Response A Reponse instance + */ + public function error($message, $code = 500) + { + $this->template->assign_vars(array( + 'MESSAGE_TEXT' => $message, + 'MESSAGE_TITLE' => $this->user->lang('INFORMATION'), + )); + + return $this->render('message_body.html', $this->user->lang('INFORMATION'), $code); + } +} diff --git a/phpBB/phpbb/controller/provider.php b/phpBB/phpbb/controller/provider.php new file mode 100644 index 0000000000..b2a5b9f6b2 --- /dev/null +++ b/phpBB/phpbb/controller/provider.php @@ -0,0 +1,82 @@ +routing_paths = $routing_paths; + } + + /** + * Locate paths containing routing files + * This sets an internal property but does not return the paths. + * + * @return The current instance of this object for method chaining + */ + public function import_paths_from_finder(phpbb_extension_finder $finder) + { + // We hardcode the path to the core config directory + // because the finder cannot find it + $this->routing_paths = array_merge(array('config'), array_map('dirname', array_keys($finder + ->directory('config') + ->prefix('routing') + ->suffix('yml') + ->find() + ))); + + return $this; + } + + /** + * Get a list of controllers and return it + * + * @param string $base_path Base path to prepend to file paths + * @return array Array of controllers and their route information + */ + public function find($base_path = '') + { + $routes = new RouteCollection; + foreach ($this->routing_paths as $path) + { + $loader = new YamlFileLoader(new FileLocator($base_path . $path)); + $routes->addCollection($loader->load('routing.yml')); + } + + return $routes; + } +} diff --git a/phpBB/phpbb/controller/resolver.php b/phpBB/phpbb/controller/resolver.php new file mode 100644 index 0000000000..95dfc8da8e --- /dev/null +++ b/phpBB/phpbb/controller/resolver.php @@ -0,0 +1,154 @@ +user = $user; + $this->container = $container; + $this->style = $style; + } + + /** + * Load a controller callable + * + * @param Symfony\Component\HttpFoundation\Request $request Symfony Request object + * @return bool|Callable Callable or false + * @throws phpbb_controller_exception + */ + public function getController(Request $request) + { + $controller = $request->attributes->get('_controller'); + + if (!$controller) + { + throw new phpbb_controller_exception($this->user->lang['CONTROLLER_NOT_SPECIFIED']); + } + + // Require a method name along with the service name + if (stripos($controller, ':') === false) + { + throw new phpbb_controller_exception($this->user->lang['CONTROLLER_METHOD_NOT_SPECIFIED']); + } + + list($service, $method) = explode(':', $controller); + + if (!$this->container->has($service)) + { + throw new phpbb_controller_exception($this->user->lang('CONTROLLER_SERVICE_UNDEFINED', $service)); + } + + $controller_object = $this->container->get($service); + + /* + * If this is an extension controller, we'll try to automatically set + * the style paths for the extension (the ext author can change them + * if necessary). + */ + $controller_dir = explode('_', get_class($controller_object)); + + // 0 phpbb, 1 ext, 2 vendor, 3 extension name, ... + if (!is_null($this->style) && isset($controller_dir[3]) && $controller_dir[1] === 'ext') + { + $controller_style_dir = 'ext/' . $controller_dir[2] . '/' . $controller_dir[3] . '/styles'; + + if (is_dir($controller_style_dir)) + { + $this->style->set_style(array($controller_style_dir, 'styles')); + } + } + + return array($controller_object, $method); + } + + /** + * Dependencies should be specified in the service definition and can be + * then accessed in __construct(). Arguments are sent through the URL path + * and should match the parameters of the method you are using as your + * controller. + * + * @param Symfony\Component\HttpFoundation\Request $request Symfony Request object + * @param mixed $controller A callable (controller class, method) + * @return bool False + * @throws phpbb_controller_exception + */ + public function getArguments(Request $request, $controller) + { + // At this point, $controller contains the object and method name + list($object, $method) = $controller; + $mirror = new ReflectionMethod($object, $method); + + $arguments = array(); + $parameters = $mirror->getParameters(); + $attributes = $request->attributes->all(); + foreach ($parameters as $param) + { + if (array_key_exists($param->name, $attributes)) + { + $arguments[] = $attributes[$param->name]; + } + else if ($param->getClass() && $param->getClass()->isInstance($request)) + { + $arguments[] = $request; + } + else if ($param->isDefaultValueAvailable()) + { + $arguments[] = $param->getDefaultValue(); + } + else + { + throw new phpbb_controller_exception($this->user->lang('CONTROLLER_ARGUMENT_VALUE_MISSING', $param->getPosition() + 1, get_class($object) . ':' . $method, $param->name)); + } + } + + return $arguments; + } +} diff --git a/phpBB/phpbb/cron/manager.php b/phpBB/phpbb/cron/manager.php new file mode 100644 index 0000000000..84c9650830 --- /dev/null +++ b/phpBB/phpbb/cron/manager.php @@ -0,0 +1,138 @@ +phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + + $this->load_tasks($tasks); + } + + /** + * Loads tasks given by name, wraps them + * and puts them into $this->tasks. + * + * @param array|Traversable $tasks Array of instances of phpbb_cron_task + * + * @return null + */ + public function load_tasks($tasks) + { + foreach ($tasks as $task) + { + $this->tasks[] = $this->wrap_task($task); + } + } + + /** + * Finds a task that is ready to run. + * + * If several tasks are ready, any one of them could be returned. + * + * If no tasks are ready, null is returned. + * + * @return phpbb_cron_task_wrapper|null + */ + public function find_one_ready_task() + { + foreach ($this->tasks as $task) + { + if ($task->is_ready()) + { + return $task; + } + } + return null; + } + + /** + * Finds all tasks that are ready to run. + * + * @return array List of tasks which are ready to run (wrapped in phpbb_cron_task_wrapper). + */ + public function find_all_ready_tasks() + { + $tasks = array(); + foreach ($this->tasks as $task) + { + if ($task->is_ready()) + { + $tasks[] = $task; + } + } + return $tasks; + } + + /** + * Finds a task by name. + * + * If there is no task with the specified name, null is returned. + * + * Web runner uses this method to resolve names to tasks. + * + * @param string $name Name of the task to look up. + * @return phpbb_cron_task A task corresponding to the given name, or null. + */ + public function find_task($name) + { + foreach ($this->tasks as $task) + { + if ($task->get_name() == $name) + { + return $task; + } + } + return null; + } + + /** + * Wraps a task inside an instance of phpbb_cron_task_wrapper. + * + * @param phpbb_cron_task $task The task. + * @return phpbb_cron_task_wrapper The wrapped task. + */ + public function wrap_task(phpbb_cron_task $task) + { + return new phpbb_cron_task_wrapper($task, $this->phpbb_root_path, $this->php_ext); + } +} diff --git a/phpBB/phpbb/cron/task/base.php b/phpBB/phpbb/cron/task/base.php new file mode 100644 index 0000000000..94a2f267b4 --- /dev/null +++ b/phpBB/phpbb/cron/task/base.php @@ -0,0 +1,76 @@ +name; + } + + /** + * Sets the name of the task. + * + * @param string $name The task name + */ + public function set_name($name) + { + $this->name = $name; + } + + /** + * Returns whether this cron task can run, given current board configuration. + * + * For example, a cron task that prunes forums can only run when + * forum pruning is enabled. + * + * @return bool + */ + public function is_runnable() + { + return true; + } + + /** + * Returns whether this cron task should run now, because enough time + * has passed since it was last run. + * + * @return bool + */ + public function should_run() + { + return true; + } +} diff --git a/phpBB/phpbb/cron/task/core/prune_all_forums.php b/phpBB/phpbb/cron/task/core/prune_all_forums.php new file mode 100644 index 0000000000..2c5d38cec0 --- /dev/null +++ b/phpBB/phpbb/cron/task/core/prune_all_forums.php @@ -0,0 +1,93 @@ +phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + $this->config = $config; + $this->db = $db; + } + + /** + * Runs this cron task. + * + * @return null + */ + public function run() + { + if (!function_exists('auto_prune')) + { + include($this->phpbb_root_path . 'includes/functions_admin.' . $this->php_ext); + } + + $sql = 'SELECT forum_id, prune_next, enable_prune, prune_days, prune_viewed, forum_flags, prune_freq + FROM ' . FORUMS_TABLE . " + WHERE enable_prune = 1 + AND prune_next < " . time(); + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + if ($row['prune_days']) + { + auto_prune($row['forum_id'], 'posted', $row['forum_flags'], $row['prune_days'], $row['prune_freq']); + } + + if ($row['prune_viewed']) + { + auto_prune($row['forum_id'], 'viewed', $row['forum_flags'], $row['prune_viewed'], $row['prune_freq']); + } + } + $this->db->sql_freeresult($result); + } + + /** + * Returns whether this cron task can run, given current board configuration. + * + * This cron task will only run when system cron is utilised. + * + * @return bool + */ + public function is_runnable() + { + return (bool) $this->config['use_system_cron']; + } +} diff --git a/phpBB/phpbb/cron/task/core/prune_forum.php b/phpBB/phpbb/cron/task/core/prune_forum.php new file mode 100644 index 0000000000..e3c497f072 --- /dev/null +++ b/phpBB/phpbb/cron/task/core/prune_forum.php @@ -0,0 +1,163 @@ +phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + $this->config = $config; + $this->db = $db; + } + + /** + * Manually set forum data. + * + * @param array $forum_data Information about a forum to be pruned. + */ + public function set_forum_data($forum_data) + { + $this->forum_data = $forum_data; + } + + /** + * Runs this cron task. + * + * @return null + */ + public function run() + { + if (!function_exists('auto_prune')) + { + include($this->phpbb_root_path . 'includes/functions_admin.' . $this->php_ext); + } + + if ($this->forum_data['prune_days']) + { + auto_prune($this->forum_data['forum_id'], 'posted', $this->forum_data['forum_flags'], $this->forum_data['prune_days'], $this->forum_data['prune_freq']); + } + + if ($this->forum_data['prune_viewed']) + { + auto_prune($this->forum_data['forum_id'], 'viewed', $this->forum_data['forum_flags'], $this->forum_data['prune_viewed'], $this->forum_data['prune_freq']); + } + } + + /** + * Returns whether this cron task can run, given current board configuration. + * + * This cron task will not run when system cron is utilised, as in + * such cases prune_all_forums task would run instead. + * + * Additionally, this task must be given the forum data, either via + * the constructor or parse_parameters method. + * + * @return bool + */ + public function is_runnable() + { + return !$this->config['use_system_cron'] && $this->forum_data; + } + + /** + * Returns whether this cron task should run now, because enough time + * has passed since it was last run. + * + * Forum pruning interval is specified in the forum data. + * + * @return bool + */ + public function should_run() + { + return $this->forum_data['enable_prune'] && $this->forum_data['prune_next'] < time(); + } + + /** + * Returns parameters of this cron task as an array. + * The array has one key, f, whose value is id of the forum to be pruned. + * + * @return array + */ + public function get_parameters() + { + return array('f' => $this->forum_data['forum_id']); + } + + /** + * Parses parameters found in $request, which is an instance of + * phpbb_request_interface. + * + * It is expected to have a key f whose value is id of the forum to be pruned. + * + * @param phpbb_request_interface $request Request object. + * + * @return null + */ + public function parse_parameters(phpbb_request_interface $request) + { + $this->forum_data = null; + if ($request->is_set('f')) + { + $forum_id = $request->variable('f', 0); + + $sql = 'SELECT forum_id, prune_next, enable_prune, prune_days, prune_viewed, forum_flags, prune_freq + FROM ' . FORUMS_TABLE . " + WHERE forum_id = $forum_id"; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if ($row) + { + $this->forum_data = $row; + } + } + } +} diff --git a/phpBB/phpbb/cron/task/core/queue.php b/phpBB/phpbb/cron/task/core/queue.php new file mode 100644 index 0000000000..732f9c6bea --- /dev/null +++ b/phpBB/phpbb/cron/task/core/queue.php @@ -0,0 +1,82 @@ +phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + $this->config = $config; + } + + /** + * Runs this cron task. + * + * @return null + */ + public function run() + { + if (!class_exists('queue')) + { + include($this->phpbb_root_path . 'includes/functions_messenger.' . $this->php_ext); + } + $queue = new queue(); + $queue->process(); + } + + /** + * Returns whether this cron task can run, given current board configuration. + * + * Queue task is only run if the email queue (file) exists. + * + * @return bool + */ + public function is_runnable() + { + return file_exists($this->phpbb_root_path . 'cache/queue.' . $this->php_ext); + } + + /** + * Returns whether this cron task should run now, because enough time + * has passed since it was last run. + * + * The interval between queue runs is specified in board configuration. + * + * @return bool + */ + public function should_run() + { + return $this->config['last_queue_run'] < time() - $this->config['queue_interval_config']; + } +} diff --git a/phpBB/phpbb/cron/task/core/tidy_cache.php b/phpBB/phpbb/cron/task/core/tidy_cache.php new file mode 100644 index 0000000000..16a45dae7c --- /dev/null +++ b/phpBB/phpbb/cron/task/core/tidy_cache.php @@ -0,0 +1,76 @@ +config = $config; + $this->cache = $cache; + } + + /** + * Runs this cron task. + * + * @return null + */ + public function run() + { + $this->cache->tidy(); + } + + /** + * Returns whether this cron task can run, given current board configuration. + * + * Tidy cache cron task runs if the cache implementation in use + * supports tidying. + * + * @return bool + */ + public function is_runnable() + { + return true; + } + + /** + * Returns whether this cron task should run now, because enough time + * has passed since it was last run. + * + * The interval between cache tidying is specified in board + * configuration. + * + * @return bool + */ + public function should_run() + { + return $this->config['cache_last_gc'] < time() - $this->config['cache_gc']; + } +} diff --git a/phpBB/phpbb/cron/task/core/tidy_database.php b/phpBB/phpbb/cron/task/core/tidy_database.php new file mode 100644 index 0000000000..b882e7b500 --- /dev/null +++ b/phpBB/phpbb/cron/task/core/tidy_database.php @@ -0,0 +1,70 @@ +phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + $this->config = $config; + } + + /** + * Runs this cron task. + * + * @return null + */ + public function run() + { + if (!function_exists('tidy_database')) + { + include($this->phpbb_root_path . 'includes/functions_admin.' . $this->php_ext); + } + tidy_database(); + } + + /** + * Returns whether this cron task should run now, because enough time + * has passed since it was last run. + * + * The interval between database tidying is specified in board + * configuration. + * + * @return bool + */ + public function should_run() + { + return $this->config['database_last_gc'] < time() - $this->config['database_gc']; + } +} diff --git a/phpBB/phpbb/cron/task/core/tidy_search.php b/phpBB/phpbb/cron/task/core/tidy_search.php new file mode 100644 index 0000000000..a3d5b7dbd2 --- /dev/null +++ b/phpBB/phpbb/cron/task/core/tidy_search.php @@ -0,0 +1,104 @@ +phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + $this->auth = $auth; + $this->config = $config; + $this->db = $db; + $this->user = $user; + } + + /** + * Runs this cron task. + * + * @return null + */ + public function run() + { + // Select the search method + $search_type = basename($this->config['search_type']); + + // We do some additional checks in the module to ensure it can actually be utilised + $error = false; + $search = new $search_type($error, $this->phpbb_root_path, $this->php_ext, $this->auth, $this->config, $this->db, $this->user); + + if (!$error) + { + $search->tidy(); + } + } + + /** + * Returns whether this cron task can run, given current board configuration. + * + * Search cron task is runnable in all normal use. It may not be + * runnable if the search backend implementation selected in board + * configuration does not exist. + * + * @return bool + */ + public function is_runnable() + { + // Select the search method + $search_type = basename($this->config['search_type']); + + return class_exists($search_type); + } + + /** + * Returns whether this cron task should run now, because enough time + * has passed since it was last run. + * + * The interval between search tidying is specified in board + * configuration. + * + * @return bool + */ + public function should_run() + { + return $this->config['search_last_gc'] < time() - $this->config['search_gc']; + } +} diff --git a/phpBB/phpbb/cron/task/core/tidy_sessions.php b/phpBB/phpbb/cron/task/core/tidy_sessions.php new file mode 100644 index 0000000000..95f55235c9 --- /dev/null +++ b/phpBB/phpbb/cron/task/core/tidy_sessions.php @@ -0,0 +1,63 @@ +config = $config; + $this->user = $user; + } + + /** + * Runs this cron task. + * + * @return null + */ + public function run() + { + $this->user->session_gc(); + } + + /** + * Returns whether this cron task should run now, because enough time + * has passed since it was last run. + * + * The interval between session tidying is specified in board + * configuration. + * + * @return bool + */ + public function should_run() + { + return $this->config['session_last_gc'] < time() - $this->config['session_gc']; + } +} diff --git a/phpBB/phpbb/cron/task/core/tidy_warnings.php b/phpBB/phpbb/cron/task/core/tidy_warnings.php new file mode 100644 index 0000000000..2a7798e56e --- /dev/null +++ b/phpBB/phpbb/cron/task/core/tidy_warnings.php @@ -0,0 +1,84 @@ +phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + $this->config = $config; + } + + /** + * Runs this cron task. + * + * @return null + */ + public function run() + { + if (!function_exists('tidy_warnings')) + { + include($this->phpbb_root_path . 'includes/functions_admin.' . $this->php_ext); + } + tidy_warnings(); + } + + /** + * Returns whether this cron task can run, given current board configuration. + * + * If warnings are set to never expire, this cron task will not run. + * + * @return bool + */ + public function is_runnable() + { + return (bool) $this->config['warnings_expire_days']; + } + + /** + * Returns whether this cron task should run now, because enough time + * has passed since it was last run. + * + * The interval between warnings tidying is specified in board + * configuration. + * + * @return bool + */ + public function should_run() + { + return $this->config['warnings_last_gc'] < time() - $this->config['warnings_gc']; + } +} diff --git a/phpBB/phpbb/cron/task/parametrized.php b/phpBB/phpbb/cron/task/parametrized.php new file mode 100644 index 0000000000..5f0e46eafc --- /dev/null +++ b/phpBB/phpbb/cron/task/parametrized.php @@ -0,0 +1,52 @@ +task = $task; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + } + + /** + * Returns whether the wrapped task is parametrised. + * + * Parametrized tasks accept parameters during initialization and must + * normally be scheduled with parameters. + * + * @return bool Whether or not this task is parametrized. + */ + public function is_parametrized() + { + return $this->task instanceof phpbb_cron_task_parametrized; + } + + /** + * Returns whether the wrapped task is ready to run. + * + * A task is ready to run when it is runnable according to current configuration + * and enough time has passed since it was last run. + * + * @return bool Whether the wrapped task is ready to run. + */ + public function is_ready() + { + return $this->task->is_runnable() && $this->task->should_run(); + } + + /** + * Returns a url through which this task may be invoked via web. + * + * When system cron is not in use, running a cron task is accomplished + * by outputting an image with the url returned by this function as + * source. + * + * @return string URL through which this task may be invoked. + */ + public function get_url() + { + $name = $this->get_name(); + if ($this->is_parametrized()) + { + $params = $this->task->get_parameters(); + $extra = ''; + foreach ($params as $key => $value) + { + $extra .= '&' . $key . '=' . urlencode($value); + } + } + else + { + $extra = ''; + } + $url = append_sid($this->phpbb_root_path . 'cron.' . $this->php_ext, 'cron_type=' . $name . $extra); + return $url; + } + + /** + * Forwards all other method calls to the wrapped task implementation. + * + * @return mixed + */ + public function __call($name, $args) + { + return call_user_func_array(array($this->task, $name), $args); + } +} diff --git a/phpBB/phpbb/datetime.php b/phpBB/phpbb/datetime.php new file mode 100644 index 0000000000..3c6d4971b9 --- /dev/null +++ b/phpBB/phpbb/datetime.php @@ -0,0 +1,158 @@ +user = $user; + $timezone = $timezone ?: $this->user->timezone; + + parent::__construct($time, $timezone); + } + + /** + * Formats the current date time into the specified format + * + * @param string $format Optional format to use for output, defaults to users chosen format + * @param boolean $force_absolute Force output of a non relative date + * @return string Formatted date time + */ + public function format($format = '', $force_absolute = false) + { + $format = $format ? $format : $this->user->date_format; + $format = self::format_cache($format, $this->user); + $relative = ($format['is_short'] && !$force_absolute); + $now = new self($this->user, 'now', $this->user->timezone); + + $timestamp = $this->getTimestamp(); + $now_ts = $now->getTimeStamp(); + + $delta = $now_ts - $timestamp; + + if ($relative) + { + /* + * Check the delta is less than or equal to 1 hour + * and the delta not more than a minute in the past + * and the delta is either greater than -5 seconds or timestamp + * and current time are of the same minute (they must be in the same hour already) + * finally check that relative dates are supported by the language pack + */ + if ($delta <= 3600 && $delta > -60 && + ($delta >= -5 || (($now_ts / 60) % 60) == (($timestamp / 60) % 60)) + && isset($this->user->lang['datetime']['AGO'])) + { + return $this->user->lang(array('datetime', 'AGO'), max(0, (int) floor($delta / 60))); + } + else + { + $midnight = clone $now; + $midnight->setTime(0, 0, 0); + + $midnight = $midnight->getTimestamp(); + + $day = false; + + if ($timestamp > $midnight + 86400) + { + $day = 'TOMORROW'; + } + else if ($timestamp > $midnight) + { + $day = 'TODAY'; + } + else if ($timestamp > $midnight - 86400) + { + $day = 'YESTERDAY'; + } + + if ($day !== false) + { + // Format using the short formatting and finally swap out the relative token placeholder with the correct value + return str_replace(self::RELATIVE_WRAPPER . self::RELATIVE_WRAPPER, $this->user->lang['datetime'][$day], strtr(parent::format($format['format_short']), $format['lang'])); + } + } + } + + return strtr(parent::format($format['format_long']), $format['lang']); + } + + /** + * Magic method to convert DateTime object to string + * + * @return Formatted date time, according to the users default settings. + */ + public function __toString() + { + return $this->format(); + } + + /** + * Pre-processes the specified date format + * + * @param string $format Output format + * @param user $user User object to use for localisation + * @return array Processed date format + */ + static protected function format_cache($format, $user) + { + $lang = $user->lang_name; + + if (!isset(self::$format_cache[$lang])) + { + self::$format_cache[$lang] = array(); + } + + if (!isset(self::$format_cache[$lang][$format])) + { + // Is the user requesting a friendly date format (i.e. 'Today 12:42')? + self::$format_cache[$lang][$format] = array( + 'is_short' => strpos($format, self::RELATIVE_WRAPPER) !== false, + 'format_short' => substr($format, 0, strpos($format, self::RELATIVE_WRAPPER)) . self::RELATIVE_WRAPPER . self::RELATIVE_WRAPPER . substr(strrchr($format, self::RELATIVE_WRAPPER), 1), + 'format_long' => str_replace(self::RELATIVE_WRAPPER, '', $format), + 'lang' => array_filter($user->lang['datetime'], 'is_string'), + ); + + // Short representation of month in format? Some languages use different terms for the long and short format of May + if ((strpos($format, '\M') === false && strpos($format, 'M') !== false) || (strpos($format, '\r') === false && strpos($format, 'r') !== false)) + { + self::$format_cache[$lang][$format]['lang']['May'] = $user->lang['datetime']['May_short']; + } + } + + return self::$format_cache[$lang][$format]; + } +} diff --git a/phpBB/phpbb/db/driver/driver.php b/phpBB/phpbb/db/driver/driver.php new file mode 100644 index 0000000000..08c966c07a --- /dev/null +++ b/phpBB/phpbb/db/driver/driver.php @@ -0,0 +1,1044 @@ +num_queries = array( + '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), 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) . '%'; + $this->one_char = chr(0) . '_'; + } + + /** + * return on error or display error message + */ + function sql_return_on_error($fail = false) + { + $this->sql_error_triggered = false; + $this->sql_error_sql = ''; + + $this->return_on_error = $fail; + } + + /** + * Return number of sql queries and cached sql queries used + */ + function sql_num_queries($cached = false) + { + return ($cached) ? $this->num_queries['cached'] : $this->num_queries['normal']; + } + + /** + * Add to query count + */ + function sql_add_num_queries($cached = false) + { + $this->num_queries['cached'] += ($cached !== false) ? 1 : 0; + $this->num_queries['normal'] += ($cached !== false) ? 0 : 1; + $this->num_queries['total'] += 1; + } + + /** + * DBAL garbage collection, close sql connection + */ + function sql_close() + { + if (!$this->db_connect_id) + { + return false; + } + + if ($this->transaction) + { + do + { + $this->sql_transaction('commit'); + } + while ($this->transaction); + } + + foreach ($this->open_queries as $query_id) + { + $this->sql_freeresult($query_id); + } + + // Connection closed correctly. Set db_connect_id to false to prevent errors + if ($result = $this->_sql_close()) + { + $this->db_connect_id = false; + } + + return $result; + } + + /** + * Build LIMIT query + * Doing some validation here. + */ + function sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) + { + if (empty($query)) + { + return false; + } + + // Never use a negative total or offset + $total = ($total < 0) ? 0 : $total; + $offset = ($offset < 0) ? 0 : $offset; + + return $this->_sql_query_limit($query, $total, $offset, $cache_ttl); + } + + /** + * Fetch all rows + */ + function sql_fetchrowset($query_id = false) + { + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($query_id !== false) + { + $result = array(); + while ($row = $this->sql_fetchrow($query_id)) + { + $result[] = $row; + } + + return $result; + } + + return false; + } + + /** + * Seek to given row number + * rownum is zero-based + */ + function sql_rowseek($rownum, &$query_id) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($cache && $cache->sql_exists($query_id)) + { + return $cache->sql_rowseek($rownum, $query_id); + } + + if ($query_id === false) + { + return false; + } + + $this->sql_freeresult($query_id); + $query_id = $this->sql_query($this->last_query_text); + + if ($query_id === false) + { + return false; + } + + // We do not fetch the row for rownum == 0 because then the next resultset would be the second row + for ($i = 0; $i < $rownum; $i++) + { + if (!$this->sql_fetchrow($query_id)) + { + return false; + } + } + + return true; + } + + /** + * Fetch field + * if rownum is false, the current row is used, else it is pointing to the row (zero-based) + */ + function sql_fetchfield($field, $rownum = false, $query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($query_id !== false) + { + if ($rownum !== false) + { + $this->sql_rowseek($rownum, $query_id); + } + + if ($cache && !is_object($query_id) && $cache->sql_exists($query_id)) + { + return $cache->sql_fetchfield($query_id, $field); + } + + $row = $this->sql_fetchrow($query_id); + return (isset($row[$field])) ? $row[$field] : false; + } + + return false; + } + + /** + * Correctly adjust LIKE expression for special characters + * Some DBMS are handling them in a different way + * + * @param string $expression The expression to use. Every wildcard is escaped, except $this->any_char and $this->one_char + * @return string LIKE expression including the keyword! + */ + function sql_like_expression($expression) + { + $expression = utf8_str_replace(array('_', '%'), array("\_", "\%"), $expression); + $expression = utf8_str_replace(array(chr(0) . "\_", chr(0) . "\%"), array('_', '%'), $expression); + + return $this->_sql_like_expression('LIKE \'' . $this->sql_escape($expression) . '\''); + } + + /** + * Build a case expression + * + * Note: The two statements action_true and action_false must have the same data type (int, vchar, ...) in the database! + * + * @param string $condition The condition which must be true, to use action_true rather then action_else + * @param string $action_true SQL expression that is used, if the condition is true + * @param string $action_else SQL expression that is used, if the condition is false, optional + * @return string CASE expression including the condition and statements + */ + public function sql_case($condition, $action_true, $action_false = false) + { + $sql_case = 'CASE WHEN ' . $condition; + $sql_case .= ' THEN ' . $action_true; + $sql_case .= ($action_false !== false) ? ' ELSE ' . $action_false : ''; + $sql_case .= ' END'; + return $sql_case; + } + + /** + * Build a concatenated expression + * + * @param string $expr1 Base SQL expression where we append the second one + * @param string $expr2 SQL expression that is appended to the first expression + * @return string Concatenated string + */ + public function sql_concatenate($expr1, $expr2) + { + return $expr1 . ' || ' . $expr2; + } + + /** + * Returns whether results of a query need to be buffered to run a transaction while iterating over them. + * + * @return bool Whether buffering is required. + */ + function sql_buffer_nested_transactions() + { + return false; + } + + /** + * SQL Transaction + * @access private + */ + function sql_transaction($status = 'begin') + { + switch ($status) + { + case 'begin': + // If we are within a transaction we will not open another one, but enclose the current one to not loose data (prevening auto commit) + if ($this->transaction) + { + $this->transactions++; + return true; + } + + $result = $this->_sql_transaction('begin'); + + if (!$result) + { + $this->sql_error(); + } + + $this->transaction = true; + break; + + case 'commit': + // If there was a previously opened transaction we do not commit yet... but count back the number of inner transactions + if ($this->transaction && $this->transactions) + { + $this->transactions--; + return true; + } + + // Check if there is a transaction (no transaction can happen if there was an error, with a combined rollback and error returning enabled) + // This implies we have transaction always set for autocommit db's + if (!$this->transaction) + { + return false; + } + + $result = $this->_sql_transaction('commit'); + + if (!$result) + { + $this->sql_error(); + } + + $this->transaction = false; + $this->transactions = 0; + break; + + case 'rollback': + $result = $this->_sql_transaction('rollback'); + $this->transaction = false; + $this->transactions = 0; + break; + + default: + $result = $this->_sql_transaction($status); + break; + } + + return $result; + } + + /** + * Build sql statement from array for insert/update/select statements + * + * Idea for this from Ikonboard + * Possible query values: INSERT, INSERT_SELECT, UPDATE, SELECT + * + */ + function sql_build_array($query, $assoc_ary = false) + { + if (!is_array($assoc_ary)) + { + return false; + } + + $fields = $values = array(); + + if ($query == 'INSERT' || $query == 'INSERT_SELECT') + { + foreach ($assoc_ary as $key => $var) + { + $fields[] = $key; + + if (is_array($var) && is_string($var[0])) + { + // This is used for INSERT_SELECT(s) + $values[] = $var[0]; + } + else + { + $values[] = $this->_sql_validate_value($var); + } + } + + $query = ($query == 'INSERT') ? ' (' . implode(', ', $fields) . ') VALUES (' . implode(', ', $values) . ')' : ' (' . implode(', ', $fields) . ') SELECT ' . implode(', ', $values) . ' '; + } + else if ($query == 'MULTI_INSERT') + { + trigger_error('The MULTI_INSERT query value is no longer supported. Please use sql_multi_insert() instead.', E_USER_ERROR); + } + else if ($query == 'UPDATE' || $query == 'SELECT') + { + $values = array(); + foreach ($assoc_ary as $key => $var) + { + $values[] = "$key = " . $this->_sql_validate_value($var); + } + $query = implode(($query == 'UPDATE') ? ', ' : ' AND ', $values); + } + + return $query; + } + + /** + * Build IN or NOT IN sql comparison string, uses <> or = on single element + * arrays to improve comparison speed + * + * @access public + * @param string $field name of the sql column that shall be compared + * @param array $array array of values that are allowed (IN) or not allowed (NOT IN) + * @param bool $negate true for NOT IN (), false for IN () (default) + * @param bool $allow_empty_set If true, allow $array to be empty, this function will return 1=1 or 1=0 then. Default to false. + */ + function sql_in_set($field, $array, $negate = false, $allow_empty_set = false) + { + if (!sizeof($array)) + { + if (!$allow_empty_set) + { + // Print the backtrace to help identifying the location of the problematic code + $this->sql_error('No values specified for SQL IN comparison'); + } + else + { + // NOT IN () actually means everything so use a tautology + if ($negate) + { + return '1=1'; + } + // IN () actually means nothing so use a contradiction + else + { + return '1=0'; + } + } + } + + if (!is_array($array)) + { + $array = array($array); + } + + if (sizeof($array) == 1) + { + @reset($array); + $var = current($array); + + return $field . ($negate ? ' <> ' : ' = ') . $this->_sql_validate_value($var); + } + else + { + return $field . ($negate ? ' NOT IN ' : ' IN ') . '(' . implode(', ', array_map(array($this, '_sql_validate_value'), $array)) . ')'; + } + } + + /** + * Run binary AND operator on DB column. + * Results in sql statement: "{$column_name} & (1 << {$bit}) {$compare}" + * + * @param string $column_name The column name to use + * @param int $bit The value to use for the AND operator, will be converted to (1 << $bit). Is used by options, using the number schema... 0, 1, 2...29 + * @param string $compare Any custom SQL code after the check (for example "= 0") + */ + function sql_bit_and($column_name, $bit, $compare = '') + { + if (method_exists($this, '_sql_bit_and')) + { + return $this->_sql_bit_and($column_name, $bit, $compare); + } + + return $column_name . ' & ' . (1 << $bit) . (($compare) ? ' ' . $compare : ''); + } + + /** + * Run binary OR operator on DB column. + * Results in sql statement: "{$column_name} | (1 << {$bit}) {$compare}" + * + * @param string $column_name The column name to use + * @param int $bit The value to use for the OR operator, will be converted to (1 << $bit). Is used by options, using the number schema... 0, 1, 2...29 + * @param string $compare Any custom SQL code after the check (for example "= 0") + */ + function sql_bit_or($column_name, $bit, $compare = '') + { + if (method_exists($this, '_sql_bit_or')) + { + return $this->_sql_bit_or($column_name, $bit, $compare); + } + + return $column_name . ' | ' . (1 << $bit) . (($compare) ? ' ' . $compare : ''); + } + + /** + * Returns SQL string to cast a string expression to an int. + * + * @param string $expression An expression evaluating to string + * @return string Expression returning an int + */ + function cast_expr_to_bigint($expression) + { + return $expression; + } + + /** + * Returns SQL string to cast an integer expression to a string. + * + * @param string $expression An expression evaluating to int + * @return string Expression returning a string + */ + function cast_expr_to_string($expression) + { + return $expression; + } + + /** + * Run LOWER() on DB column of type text (i.e. neither varchar nor char). + * + * @param string $column_name The column name to use + * + * @return string A SQL statement like "LOWER($column_name)" + */ + function sql_lower_text($column_name) + { + return "LOWER($column_name)"; + } + + /** + * Run more than one insert statement. + * + * @param string $table table name to run the statements on + * @param array $sql_ary multi-dimensional array holding the statement data. + * + * @return bool false if no statements were executed. + * @access public + */ + function sql_multi_insert($table, $sql_ary) + { + if (!sizeof($sql_ary)) + { + return false; + } + + if ($this->multi_insert) + { + $ary = array(); + foreach ($sql_ary as $id => $_sql_ary) + { + // If by accident the sql array is only one-dimensional we build a normal insert statement + if (!is_array($_sql_ary)) + { + return $this->sql_query('INSERT INTO ' . $table . ' ' . $this->sql_build_array('INSERT', $sql_ary)); + } + + $values = array(); + foreach ($_sql_ary as $key => $var) + { + $values[] = $this->_sql_validate_value($var); + } + $ary[] = '(' . implode(', ', $values) . ')'; + } + + return $this->sql_query('INSERT INTO ' . $table . ' ' . ' (' . implode(', ', array_keys($sql_ary[0])) . ') VALUES ' . implode(', ', $ary)); + } + else + { + foreach ($sql_ary as $ary) + { + if (!is_array($ary)) + { + return false; + } + + $result = $this->sql_query('INSERT INTO ' . $table . ' ' . $this->sql_build_array('INSERT', $ary)); + + if (!$result) + { + return false; + } + } + } + + return true; + } + + /** + * Function for validating values + * @access private + */ + function _sql_validate_value($var) + { + if (is_null($var)) + { + return 'NULL'; + } + else if (is_string($var)) + { + return "'" . $this->sql_escape($var) . "'"; + } + else + { + return (is_bool($var)) ? intval($var) : $var; + } + } + + /** + * Build sql statement from array for select and select distinct statements + * + * Possible query values: SELECT, SELECT_DISTINCT + */ + function sql_build_query($query, $array) + { + $sql = ''; + switch ($query) + { + case 'SELECT': + case 'SELECT_DISTINCT'; + + $sql = str_replace('_', ' ', $query) . ' ' . $array['SELECT'] . ' FROM '; + + // Build table array. We also build an alias array for later checks. + $table_array = $aliases = array(); + $used_multi_alias = false; + + foreach ($array['FROM'] as $table_name => $alias) + { + if (is_array($alias)) + { + $used_multi_alias = true; + + foreach ($alias as $multi_alias) + { + $table_array[] = $table_name . ' ' . $multi_alias; + $aliases[] = $multi_alias; + } + } + else + { + $table_array[] = $table_name . ' ' . $alias; + $aliases[] = $alias; + } + } + + // We run the following code to determine if we need to re-order the table array. ;) + // The reason for this is that for multi-aliased tables (two equal tables) in the FROM statement the last table need to match the first comparison. + // DBMS who rely on this: Oracle, PostgreSQL and MSSQL. For all other DBMS it makes absolutely no difference in which order the table is. + if (!empty($array['LEFT_JOIN']) && sizeof($array['FROM']) > 1 && $used_multi_alias !== false) + { + // Take first LEFT JOIN + $join = current($array['LEFT_JOIN']); + + // Determine the table used there (even if there are more than one used, we only want to have one + preg_match('/(' . implode('|', $aliases) . ')\.[^\s]+/U', str_replace(array('(', ')', 'AND', 'OR', ' '), '', $join['ON']), $matches); + + // If there is a first join match, we need to make sure the table order is correct + if (!empty($matches[1])) + { + $first_join_match = trim($matches[1]); + $table_array = $last = array(); + + foreach ($array['FROM'] as $table_name => $alias) + { + if (is_array($alias)) + { + foreach ($alias as $multi_alias) + { + ($multi_alias === $first_join_match) ? $last[] = $table_name . ' ' . $multi_alias : $table_array[] = $table_name . ' ' . $multi_alias; + } + } + else + { + ($alias === $first_join_match) ? $last[] = $table_name . ' ' . $alias : $table_array[] = $table_name . ' ' . $alias; + } + } + + $table_array = array_merge($table_array, $last); + } + } + + $sql .= $this->_sql_custom_build('FROM', implode(' CROSS JOIN ', $table_array)); + + if (!empty($array['LEFT_JOIN'])) + { + foreach ($array['LEFT_JOIN'] as $join) + { + $sql .= ' LEFT JOIN ' . key($join['FROM']) . ' ' . current($join['FROM']) . ' ON (' . $join['ON'] . ')'; + } + } + + if (!empty($array['WHERE'])) + { + $sql .= ' WHERE ' . $this->_sql_custom_build('WHERE', $array['WHERE']); + } + + if (!empty($array['GROUP_BY'])) + { + $sql .= ' GROUP BY ' . $array['GROUP_BY']; + } + + if (!empty($array['ORDER_BY'])) + { + $sql .= ' ORDER BY ' . $array['ORDER_BY']; + } + + break; + } + + return $sql; + } + + /** + * display sql error page + */ + function sql_error($sql = '') + { + global $auth, $user, $config; + + // Set var to retrieve errored status + $this->sql_error_triggered = true; + $this->sql_error_sql = $sql; + + $this->sql_error_returned = $this->_sql_error(); + + if (!$this->return_on_error) + { + $message = 'SQL ERROR [ ' . $this->sql_layer . ' ]

' . $this->sql_error_returned['message'] . ' [' . $this->sql_error_returned['code'] . ']'; + + // Show complete SQL error and path to administrators only + // Additionally show complete error on installation or if extended debug mode is enabled + // The DEBUG constant is for development only! + if ((isset($auth) && $auth->acl_get('a_')) || defined('IN_INSTALL') || defined('DEBUG')) + { + $message .= ($sql) ? '

SQL

' . htmlspecialchars($sql) : ''; + } + else + { + // If error occurs in initiating the session we need to use a pre-defined language string + // This could happen if the connection could not be established for example (then we are not able to grab the default language) + if (!isset($user->lang['SQL_ERROR_OCCURRED'])) + { + $message .= '

An sql error occurred while fetching this page. Please contact an administrator if this problem persists.'; + } + else + { + if (!empty($config['board_contact'])) + { + $message .= '

' . sprintf($user->lang['SQL_ERROR_OCCURRED'], '', ''); + } + else + { + $message .= '

' . sprintf($user->lang['SQL_ERROR_OCCURRED'], '', ''); + } + } + } + + if ($this->transaction) + { + $this->sql_transaction('rollback'); + } + + if (strlen($message) > 1024) + { + // We need to define $msg_long_text here to circumvent text stripping. + global $msg_long_text; + $msg_long_text = $message; + + trigger_error(false, E_USER_ERROR); + } + + trigger_error($message, E_USER_ERROR); + } + + if ($this->transaction) + { + $this->sql_transaction('rollback'); + } + + return $this->sql_error_returned; + } + + /** + * Explain queries + */ + function sql_report($mode, $query = '') + { + global $cache, $starttime, $phpbb_root_path, $phpbb_admin_path, $user; + global $request; + + if (is_object($request) && !$request->variable('explain', false)) + { + return false; + } + + if (!$query && $this->query_hold != '') + { + $query = $this->query_hold; + } + + switch ($mode) + { + case 'display': + if (!empty($cache)) + { + $cache->unload(); + } + $this->sql_close(); + + $mtime = explode(' ', microtime()); + $totaltime = $mtime[0] + $mtime[1] - $starttime; + + echo ' + + + + SQL Report + + + +
+ +
+
+
+ +
+

SQL Report

+
+

Page generated in ' . round($totaltime, 4) . " seconds with {$this->num_queries['normal']} queries" . (($this->num_queries['cached']) ? " + {$this->num_queries['cached']} " . (($this->num_queries['cached'] == 1) ? 'query' : 'queries') . ' returning data from cache' : '') . '

+ +

Time spent on ' . $this->sql_layer . ' queries: ' . round($this->sql_time, 5) . 's | Time spent on PHP: ' . round($totaltime - $this->sql_time, 5) . 's

+ +

+ ' . $this->sql_report . ' +
+ +
+
+
+ +
+ + '; + + exit_handler(); + + break; + + case 'stop': + $endtime = explode(' ', microtime()); + $endtime = $endtime[0] + $endtime[1]; + + $this->sql_report .= ' + + + + + + + + + + + + +
Query #' . $this->num_queries['total'] . '
+ + ' . $this->html_hold . ' + +

+ '; + + if ($this->query_result) + { + if (preg_match('/^(UPDATE|DELETE|REPLACE)/', $query)) + { + $this->sql_report .= 'Affected rows: ' . $this->sql_affectedrows($this->query_result) . ' | '; + } + $this->sql_report .= 'Before: ' . sprintf('%.5f', $this->curtime - $starttime) . 's | After: ' . sprintf('%.5f', $endtime - $starttime) . 's | Elapsed: ' . sprintf('%.5f', $endtime - $this->curtime) . 's'; + } + else + { + $error = $this->sql_error(); + $this->sql_report .= 'FAILED - ' . $this->sql_layer . ' Error ' . $error['code'] . ': ' . htmlspecialchars($error['message']); + } + + $this->sql_report .= '



'; + + $this->sql_time += $endtime - $this->curtime; + break; + + case 'start': + $this->query_hold = $query; + $this->html_hold = ''; + + $this->_sql_report($mode, $query); + + $this->curtime = explode(' ', microtime()); + $this->curtime = $this->curtime[0] + $this->curtime[1]; + + break; + + case 'add_select_row': + + $html_table = func_get_arg(2); + $row = func_get_arg(3); + + if (!$html_table && sizeof($row)) + { + $html_table = true; + $this->html_hold .= ''; + + foreach (array_keys($row) as $val) + { + $this->html_hold .= ''; + } + $this->html_hold .= ''; + } + $this->html_hold .= ''; + + $class = 'row1'; + foreach (array_values($row) as $val) + { + $class = ($class == 'row1') ? 'row2' : 'row1'; + $this->html_hold .= ''; + } + $this->html_hold .= ''; + + return $html_table; + + break; + + case 'fromcache': + + $this->_sql_report($mode, $query); + + break; + + case 'record_fromcache': + + $endtime = func_get_arg(2); + $splittime = func_get_arg(3); + + $time_cache = $endtime - $this->curtime; + $time_db = $splittime - $endtime; + $color = ($time_db > $time_cache) ? 'green' : 'red'; + + $this->sql_report .= '
' . (($val) ? ucwords(str_replace('_', ' ', $val)) : ' ') . '
' . (($val) ? $val : ' ') . '
'; + $this->sql_report .= '
Query results obtained from the cache
'; + $this->sql_report .= '

'; + $this->sql_report .= 'Before: ' . sprintf('%.5f', $this->curtime - $starttime) . 's | After: ' . sprintf('%.5f', $endtime - $starttime) . 's | Elapsed [cache]: ' . sprintf('%.5f', ($time_cache)) . 's | Elapsed [db]: ' . sprintf('%.5f', $time_db) . 's



'; + + // Pad the start time to not interfere with page timing + $starttime += $time_db; + + break; + + default: + + $this->_sql_report($mode, $query); + + break; + } + + return true; + } + + /** + * Gets the estimated number of rows in a specified table. + * + * @param string $table_name Table name + * + * @return string Number of rows in $table_name. + * Prefixed with ~ if estimated (otherwise exact). + * + * @access public + */ + function get_estimated_row_count($table_name) + { + return $this->get_row_count($table_name); + } + + /** + * Gets the exact number of rows in a specified table. + * + * @param string $table_name Table name + * + * @return string Exact number of rows in $table_name. + * + * @access public + */ + function get_row_count($table_name) + { + $sql = 'SELECT COUNT(*) AS rows_total + FROM ' . $this->sql_escape($table_name); + $result = $this->sql_query($sql); + $rows_total = $this->sql_fetchfield('rows_total'); + $this->sql_freeresult($result); + + return $rows_total; + } +} diff --git a/phpBB/phpbb/db/driver/firebird.php b/phpBB/phpbb/db/driver/firebird.php new file mode 100644 index 0000000000..787c28b812 --- /dev/null +++ b/phpBB/phpbb/db/driver/firebird.php @@ -0,0 +1,538 @@ +persistency = $persistency; + $this->user = $sqluser; + $this->server = $sqlserver . (($port) ? ':' . $port : ''); + $this->dbname = str_replace('\\', '/', $database); + + // There are three possibilities to connect to an interbase db + if (!$this->server) + { + $use_database = $this->dbname; + } + else if (strpos($this->server, '//') === 0) + { + $use_database = $this->server . $this->dbname; + } + else + { + $use_database = $this->server . ':' . $this->dbname; + } + + if ($this->persistency) + { + if (!function_exists('ibase_pconnect')) + { + $this->connect_error = 'ibase_pconnect function does not exist, is interbase extension installed?'; + return $this->sql_error(''); + } + $this->db_connect_id = @ibase_pconnect($use_database, $this->user, $sqlpassword, false, false, 3); + } + else + { + if (!function_exists('ibase_connect')) + { + $this->connect_error = 'ibase_connect function does not exist, is interbase extension installed?'; + return $this->sql_error(''); + } + $this->db_connect_id = @ibase_connect($use_database, $this->user, $sqlpassword, false, false, 3); + } + + // Do not call ibase_service_attach if connection failed, + // otherwise error message from ibase_(p)connect call will be clobbered. + if ($this->db_connect_id && function_exists('ibase_service_attach') && $this->server) + { + $this->service_handle = @ibase_service_attach($this->server, $this->user, $sqlpassword); + } + else + { + $this->service_handle = false; + } + + return ($this->db_connect_id) ? $this->db_connect_id : $this->sql_error(''); + } + + /** + * Version information about used database + * @param bool $raw if true, only return the fetched sql_server_version + * @param bool $use_cache forced to false for Interbase + * @return string sql server version + */ + function sql_server_info($raw = false, $use_cache = true) + { + /** + * force $use_cache false. I didn't research why the caching code there is no caching code + * but I assume its because the IB extension provides a direct method to access it + * without a query. + */ + + $use_cache = false; + + if ($this->service_handle !== false && function_exists('ibase_server_info')) + { + return @ibase_server_info($this->service_handle, IBASE_SVC_SERVER_VERSION); + } + + return ($raw) ? '2.1' : 'Firebird/Interbase'; + } + + /** + * SQL Transaction + * @access private + */ + function _sql_transaction($status = 'begin') + { + switch ($status) + { + case 'begin': + return true; + break; + + case 'commit': + return @ibase_commit(); + break; + + case 'rollback': + return @ibase_rollback(); + break; + } + + return true; + } + + /** + * Base query method + * + * @param string $query Contains the SQL query which shall be executed + * @param int $cache_ttl Either 0 to avoid caching or the time in seconds which the result shall be kept in cache + * @return mixed When casted to bool the returned value returns true on success and false on failure + * + * @access public + */ + function sql_query($query = '', $cache_ttl = 0) + { + if ($query != '') + { + global $cache; + + // EXPLAIN only in extra debug mode + if (defined('DEBUG')) + { + $this->sql_report('start', $query); + } + + $this->last_query_text = $query; + $this->query_result = ($cache && $cache_ttl) ? $cache->sql_load($query) : false; + $this->sql_add_num_queries($this->query_result); + + if ($this->query_result === false) + { + $array = array(); + // We overcome Firebird's 32767 char limit by binding vars + if (strlen($query) > 32767) + { + if (preg_match('/^(INSERT INTO[^(]++)\\(([^()]+)\\) VALUES[^(]++\\((.*?)\\)$/s', $query, $regs)) + { + if (strlen($regs[3]) > 32767) + { + preg_match_all('/\'(?:[^\']++|\'\')*+\'|[\d-.]+/', $regs[3], $vals, PREG_PATTERN_ORDER); + + $inserts = $vals[0]; + unset($vals); + + foreach ($inserts as $key => $value) + { + if (!empty($value) && $value[0] === "'" && strlen($value) > 32769) // check to see if this thing is greater than the max + 'x2 + { + $inserts[$key] = '?'; + $array[] = str_replace("''", "'", substr($value, 1, -1)); + } + } + + $query = $regs[1] . '(' . $regs[2] . ') VALUES (' . implode(', ', $inserts) . ')'; + } + } + else if (preg_match('/^(UPDATE ([\\w_]++)\\s+SET )([\\w_]++\\s*=\\s*(?:\'(?:[^\']++|\'\')*+\'|\\d+)(?:,\\s*[\\w_]++\\s*=\\s*(?:\'(?:[^\']++|\'\')*+\'|[\d-.]+))*+)\\s+(WHERE.*)$/s', $query, $data)) + { + if (strlen($data[3]) > 32767) + { + $update = $data[1]; + $where = $data[4]; + preg_match_all('/(\\w++)\\s*=\\s*(\'(?:[^\']++|\'\')*+\'|[\d-.]++)/', $data[3], $temp, PREG_SET_ORDER); + unset($data); + + $cols = array(); + foreach ($temp as $value) + { + if (!empty($value[2]) && $value[2][0] === "'" && strlen($value[2]) > 32769) // check to see if this thing is greater than the max + 'x2 + { + $array[] = str_replace("''", "'", substr($value[2], 1, -1)); + $cols[] = $value[1] . '=?'; + } + else + { + $cols[] = $value[1] . '=' . $value[2]; + } + } + + $query = $update . implode(', ', $cols) . ' ' . $where; + unset($cols); + } + } + } + + if (!function_exists('ibase_affected_rows') && (preg_match('/^UPDATE ([\w_]++)\s+SET [\w_]++\s*=\s*(?:\'(?:[^\']++|\'\')*+\'|[\d-.]+)(?:,\s*[\w_]++\s*=\s*(?:\'(?:[^\']++|\'\')*+\'|[\d-.]+))*+\s+(WHERE.*)?$/s', $query, $regs) || preg_match('/^DELETE FROM ([\w_]++)\s*(WHERE\s*.*)?$/s', $query, $regs))) + { + $affected_sql = 'SELECT COUNT(*) as num_rows_affected FROM ' . $regs[1]; + if (!empty($regs[2])) + { + $affected_sql .= ' ' . $regs[2]; + } + + if (!($temp_q_id = @ibase_query($this->db_connect_id, $affected_sql))) + { + return false; + } + + $temp_result = @ibase_fetch_assoc($temp_q_id); + @ibase_free_result($temp_q_id); + + $this->affected_rows = ($temp_result) ? $temp_result['NUM_ROWS_AFFECTED'] : false; + } + + if (sizeof($array)) + { + $p_query = @ibase_prepare($this->db_connect_id, $query); + array_unshift($array, $p_query); + $this->query_result = call_user_func_array('ibase_execute', $array); + unset($array); + + if ($this->query_result === false) + { + $this->sql_error($query); + } + } + else if (($this->query_result = @ibase_query($this->db_connect_id, $query)) === false) + { + $this->sql_error($query); + } + + if (defined('DEBUG')) + { + $this->sql_report('stop', $query); + } + + if (!$this->transaction) + { + if (function_exists('ibase_commit_ret')) + { + @ibase_commit_ret(); + } + else + { + // way cooler than ibase_commit_ret :D + @ibase_query('COMMIT RETAIN;'); + } + } + + if ($cache && $cache_ttl) + { + $this->open_queries[(int) $this->query_result] = $this->query_result; + $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); + } + else if (strpos($query, 'SELECT') === 0 && $this->query_result) + { + $this->open_queries[(int) $this->query_result] = $this->query_result; + } + } + else if (defined('DEBUG')) + { + $this->sql_report('fromcache', $query); + } + } + else + { + return false; + } + + return $this->query_result; + } + + /** + * Build LIMIT query + */ + function _sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) + { + $this->query_result = false; + + $query = 'SELECT FIRST ' . $total . ((!empty($offset)) ? ' SKIP ' . $offset : '') . substr($query, 6); + + return $this->sql_query($query, $cache_ttl); + } + + /** + * Return number of affected rows + */ + function sql_affectedrows() + { + // PHP 5+ function + if (function_exists('ibase_affected_rows')) + { + return ($this->db_connect_id) ? @ibase_affected_rows($this->db_connect_id) : false; + } + else + { + return $this->affected_rows; + } + } + + /** + * Fetch current row + */ + function sql_fetchrow($query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($cache && $cache->sql_exists($query_id)) + { + return $cache->sql_fetchrow($query_id); + } + + if ($query_id === false) + { + return false; + } + + $row = array(); + $cur_row = @ibase_fetch_object($query_id, IBASE_TEXT); + + if (!$cur_row) + { + return false; + } + + foreach (get_object_vars($cur_row) as $key => $value) + { + $row[strtolower($key)] = (is_string($value)) ? trim(str_replace(array("\\0", "\\n"), array("\0", "\n"), $value)) : $value; + } + + return (sizeof($row)) ? $row : false; + } + + /** + * Get last inserted id after insert statement + */ + function sql_nextid() + { + $query_id = $this->query_result; + + if ($query_id !== false && $this->last_query_text != '') + { + if ($this->query_result && preg_match('#^INSERT[\t\n ]+INTO[\t\n ]+([a-z0-9\_\-]+)#i', $this->last_query_text, $tablename)) + { + $sql = 'SELECT GEN_ID(' . $tablename[1] . '_gen, 0) AS new_id FROM RDB$DATABASE'; + + if (!($temp_q_id = @ibase_query($this->db_connect_id, $sql))) + { + return false; + } + + $temp_result = @ibase_fetch_assoc($temp_q_id); + @ibase_free_result($temp_q_id); + + return ($temp_result) ? $temp_result['NEW_ID'] : false; + } + } + + return false; + } + + /** + * Free sql result + */ + function sql_freeresult($query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($cache && $cache->sql_exists($query_id)) + { + return $cache->sql_freeresult($query_id); + } + + if (isset($this->open_queries[(int) $query_id])) + { + unset($this->open_queries[(int) $query_id]); + return @ibase_free_result($query_id); + } + + return false; + } + + /** + * Escape string used in sql query + */ + function sql_escape($msg) + { + return str_replace(array("'", "\0"), array("''", ''), $msg); + } + + /** + * Build LIKE expression + * @access private + */ + function _sql_like_expression($expression) + { + return $expression . " ESCAPE '\\'"; + } + + /** + * Build db-specific query data + * @access private + */ + function _sql_custom_build($stage, $data) + { + return $data; + } + + function _sql_bit_and($column_name, $bit, $compare = '') + { + return 'BIN_AND(' . $column_name . ', ' . (1 << $bit) . ')' . (($compare) ? ' ' . $compare : ''); + } + + function _sql_bit_or($column_name, $bit, $compare = '') + { + return 'BIN_OR(' . $column_name . ', ' . (1 << $bit) . ')' . (($compare) ? ' ' . $compare : ''); + } + + /** + * @inheritdoc + */ + function cast_expr_to_bigint($expression) + { + // Precision must be from 1 to 18 + return 'CAST(' . $expression . ' as DECIMAL(18, 0))'; + } + + /** + * @inheritdoc + */ + function cast_expr_to_string($expression) + { + return 'CAST(' . $expression . ' as VARCHAR(255))'; + } + + /** + * return sql error array + * @access private + */ + function _sql_error() + { + // Need special handling here because ibase_errmsg returns + // connection errors, however if the interbase extension + // is not installed then ibase_errmsg does not exist and + // we cannot call it. + if (function_exists('ibase_errmsg')) + { + $msg = @ibase_errmsg(); + if (!$msg) + { + $msg = $this->connect_error; + } + } + else + { + $msg = $this->connect_error; + } + return array( + 'message' => $msg, + 'code' => (@function_exists('ibase_errcode') ? @ibase_errcode() : '') + ); + } + + /** + * Close sql connection + * @access private + */ + function _sql_close() + { + if ($this->service_handle !== false) + { + @ibase_service_detach($this->service_handle); + } + + return @ibase_close($this->db_connect_id); + } + + /** + * Build db-specific report + * @access private + */ + function _sql_report($mode, $query = '') + { + switch ($mode) + { + case 'start': + break; + + case 'fromcache': + $endtime = explode(' ', microtime()); + $endtime = $endtime[0] + $endtime[1]; + + $result = @ibase_query($this->db_connect_id, $query); + while ($void = @ibase_fetch_object($result, IBASE_TEXT)) + { + // Take the time spent on parsing rows into account + } + @ibase_free_result($result); + + $splittime = explode(' ', microtime()); + $splittime = $splittime[0] + $splittime[1]; + + $this->sql_report('record_fromcache', $query, $endtime, $splittime); + + break; + } + } +} diff --git a/phpBB/phpbb/db/driver/mssql.php b/phpBB/phpbb/db/driver/mssql.php new file mode 100644 index 0000000000..89c2c2351b --- /dev/null +++ b/phpBB/phpbb/db/driver/mssql.php @@ -0,0 +1,472 @@ +connect_error = 'mssql_connect function does not exist, is mssql extension installed?'; + return $this->sql_error(''); + } + + $this->persistency = $persistency; + $this->user = $sqluser; + $this->dbname = $database; + + $port_delimiter = (defined('PHP_OS') && substr(PHP_OS, 0, 3) === 'WIN') ? ',' : ':'; + $this->server = $sqlserver . (($port) ? $port_delimiter . $port : ''); + + @ini_set('mssql.charset', 'UTF-8'); + @ini_set('mssql.textlimit', 2147483647); + @ini_set('mssql.textsize', 2147483647); + + $this->db_connect_id = ($this->persistency) ? @mssql_pconnect($this->server, $this->user, $sqlpassword, $new_link) : @mssql_connect($this->server, $this->user, $sqlpassword, $new_link); + + if ($this->db_connect_id && $this->dbname != '') + { + if (!@mssql_select_db($this->dbname, $this->db_connect_id)) + { + @mssql_close($this->db_connect_id); + return false; + } + } + + return ($this->db_connect_id) ? $this->db_connect_id : $this->sql_error(''); + } + + /** + * Version information about used database + * @param bool $raw if true, only return the fetched sql_server_version + * @param bool $use_cache If true, it is safe to retrieve the value from the cache + * @return string sql server version + */ + function sql_server_info($raw = false, $use_cache = true) + { + global $cache; + + if (!$use_cache || empty($cache) || ($this->sql_server_version = $cache->get('mssql_version')) === false) + { + $result_id = @mssql_query("SELECT SERVERPROPERTY('productversion'), SERVERPROPERTY('productlevel'), SERVERPROPERTY('edition')", $this->db_connect_id); + + $row = false; + if ($result_id) + { + $row = @mssql_fetch_assoc($result_id); + @mssql_free_result($result_id); + } + + $this->sql_server_version = ($row) ? trim(implode(' ', $row)) : 0; + + if (!empty($cache) && $use_cache) + { + $cache->put('mssql_version', $this->sql_server_version); + } + } + + if ($raw) + { + return $this->sql_server_version; + } + + return ($this->sql_server_version) ? 'MSSQL
' . $this->sql_server_version : 'MSSQL'; + } + + /** + * {@inheritDoc} + */ + public function sql_concatenate($expr1, $expr2) + { + return $expr1 . ' + ' . $expr2; + } + + /** + * SQL Transaction + * @access private + */ + function _sql_transaction($status = 'begin') + { + switch ($status) + { + case 'begin': + return @mssql_query('BEGIN TRANSACTION', $this->db_connect_id); + break; + + case 'commit': + return @mssql_query('COMMIT TRANSACTION', $this->db_connect_id); + break; + + case 'rollback': + return @mssql_query('ROLLBACK TRANSACTION', $this->db_connect_id); + break; + } + + return true; + } + + /** + * Base query method + * + * @param string $query Contains the SQL query which shall be executed + * @param int $cache_ttl Either 0 to avoid caching or the time in seconds which the result shall be kept in cache + * @return mixed When casted to bool the returned value returns true on success and false on failure + * + * @access public + */ + function sql_query($query = '', $cache_ttl = 0) + { + if ($query != '') + { + global $cache; + + // EXPLAIN only in extra debug mode + if (defined('DEBUG')) + { + $this->sql_report('start', $query); + } + + $this->query_result = ($cache && $cache_ttl) ? $cache->sql_load($query) : false; + $this->sql_add_num_queries($this->query_result); + + if ($this->query_result === false) + { + if (($this->query_result = @mssql_query($query, $this->db_connect_id)) === false) + { + $this->sql_error($query); + } + + if (defined('DEBUG')) + { + $this->sql_report('stop', $query); + } + + if ($cache && $cache_ttl) + { + $this->open_queries[(int) $this->query_result] = $this->query_result; + $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); + } + else if (strpos($query, 'SELECT') === 0 && $this->query_result) + { + $this->open_queries[(int) $this->query_result] = $this->query_result; + } + } + else if (defined('DEBUG')) + { + $this->sql_report('fromcache', $query); + } + } + else + { + return false; + } + + return $this->query_result; + } + + /** + * Build LIMIT query + */ + function _sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) + { + $this->query_result = false; + + // Since TOP is only returning a set number of rows we won't need it if total is set to 0 (return all rows) + if ($total) + { + // We need to grab the total number of rows + the offset number of rows to get the correct result + if (strpos($query, 'SELECT DISTINCT') === 0) + { + $query = 'SELECT DISTINCT TOP ' . ($total + $offset) . ' ' . substr($query, 15); + } + else + { + $query = 'SELECT TOP ' . ($total + $offset) . ' ' . substr($query, 6); + } + } + + $result = $this->sql_query($query, $cache_ttl); + + // Seek by $offset rows + if ($offset) + { + $this->sql_rowseek($offset, $result); + } + + return $result; + } + + /** + * Return number of affected rows + */ + function sql_affectedrows() + { + return ($this->db_connect_id) ? @mssql_rows_affected($this->db_connect_id) : false; + } + + /** + * Fetch current row + */ + function sql_fetchrow($query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($cache && $cache->sql_exists($query_id)) + { + return $cache->sql_fetchrow($query_id); + } + + if ($query_id === false) + { + return false; + } + + $row = @mssql_fetch_assoc($query_id); + + // I hope i am able to remove this later... hopefully only a PHP or MSSQL bug + if ($row) + { + foreach ($row as $key => $value) + { + $row[$key] = ($value === ' ' || $value === NULL) ? '' : $value; + } + } + + return $row; + } + + /** + * Seek to given row number + * rownum is zero-based + */ + function sql_rowseek($rownum, &$query_id) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($cache && $cache->sql_exists($query_id)) + { + return $cache->sql_rowseek($rownum, $query_id); + } + + return ($query_id !== false) ? @mssql_data_seek($query_id, $rownum) : false; + } + + /** + * Get last inserted id after insert statement + */ + function sql_nextid() + { + $result_id = @mssql_query('SELECT SCOPE_IDENTITY()', $this->db_connect_id); + if ($result_id) + { + if ($row = @mssql_fetch_assoc($result_id)) + { + @mssql_free_result($result_id); + return $row['computed']; + } + @mssql_free_result($result_id); + } + + return false; + } + + /** + * Free sql result + */ + function sql_freeresult($query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($cache && $cache->sql_exists($query_id)) + { + return $cache->sql_freeresult($query_id); + } + + if (isset($this->open_queries[$query_id])) + { + unset($this->open_queries[$query_id]); + return @mssql_free_result($query_id); + } + + return false; + } + + /** + * Escape string used in sql query + */ + function sql_escape($msg) + { + return str_replace(array("'", "\0"), array("''", ''), $msg); + } + + /** + * {@inheritDoc} + */ + function sql_lower_text($column_name) + { + return "LOWER(SUBSTRING($column_name, 1, DATALENGTH($column_name)))"; + } + + /** + * Build LIKE expression + * @access private + */ + function _sql_like_expression($expression) + { + return $expression . " ESCAPE '\\'"; + } + + /** + * return sql error array + * @access private + */ + function _sql_error() + { + if (function_exists('mssql_get_last_message')) + { + $error = array( + 'message' => @mssql_get_last_message(), + 'code' => '', + ); + + // Get error code number + $result_id = @mssql_query('SELECT @@ERROR as code', $this->db_connect_id); + if ($result_id) + { + $row = @mssql_fetch_assoc($result_id); + $error['code'] = $row['code']; + @mssql_free_result($result_id); + } + + // Get full error message if possible + $sql = 'SELECT CAST(description as varchar(255)) as message + FROM master.dbo.sysmessages + WHERE error = ' . $error['code']; + $result_id = @mssql_query($sql); + + if ($result_id) + { + $row = @mssql_fetch_assoc($result_id); + if (!empty($row['message'])) + { + $error['message'] .= '
' . $row['message']; + } + @mssql_free_result($result_id); + } + } + else + { + $error = array( + 'message' => $this->connect_error, + 'code' => '', + ); + } + + return $error; + } + + /** + * Build db-specific query data + * @access private + */ + function _sql_custom_build($stage, $data) + { + return $data; + } + + /** + * Close sql connection + * @access private + */ + function _sql_close() + { + return @mssql_close($this->db_connect_id); + } + + /** + * Build db-specific report + * @access private + */ + function _sql_report($mode, $query = '') + { + switch ($mode) + { + case 'start': + $html_table = false; + @mssql_query('SET SHOWPLAN_TEXT ON;', $this->db_connect_id); + if ($result = @mssql_query($query, $this->db_connect_id)) + { + @mssql_next_result($result); + while ($row = @mssql_fetch_row($result)) + { + $html_table = $this->sql_report('add_select_row', $query, $html_table, $row); + } + } + @mssql_query('SET SHOWPLAN_TEXT OFF;', $this->db_connect_id); + @mssql_free_result($result); + + if ($html_table) + { + $this->html_hold .= ''; + } + break; + + case 'fromcache': + $endtime = explode(' ', microtime()); + $endtime = $endtime[0] + $endtime[1]; + + $result = @mssql_query($query, $this->db_connect_id); + while ($void = @mssql_fetch_assoc($result)) + { + // Take the time spent on parsing rows into account + } + @mssql_free_result($result); + + $splittime = explode(' ', microtime()); + $splittime = $splittime[0] + $splittime[1]; + + $this->sql_report('record_fromcache', $query, $endtime, $splittime); + + break; + } + } +} diff --git a/phpBB/phpbb/db/driver/mssql_base.php b/phpBB/phpbb/db/driver/mssql_base.php new file mode 100644 index 0000000000..56c111c871 --- /dev/null +++ b/phpBB/phpbb/db/driver/mssql_base.php @@ -0,0 +1,65 @@ +persistency = $persistency; + $this->user = $sqluser; + $this->dbname = $database; + + $port_delimiter = (defined('PHP_OS') && substr(PHP_OS, 0, 3) === 'WIN') ? ',' : ':'; + $this->server = $sqlserver . (($port) ? $port_delimiter . $port : ''); + + $max_size = @ini_get('odbc.defaultlrl'); + if (!empty($max_size)) + { + $unit = strtolower(substr($max_size, -1, 1)); + $max_size = (int) $max_size; + + if ($unit == 'k') + { + $max_size = floor($max_size / 1024); + } + else if ($unit == 'g') + { + $max_size *= 1024; + } + else if (is_numeric($unit)) + { + $max_size = floor((int) ($max_size . $unit) / 1048576); + } + $max_size = max(8, $max_size) . 'M'; + + @ini_set('odbc.defaultlrl', $max_size); + } + + if ($this->persistency) + { + if (!function_exists('odbc_pconnect')) + { + $this->connect_error = 'odbc_pconnect function does not exist, is odbc extension installed?'; + return $this->sql_error(''); + } + $this->db_connect_id = @odbc_pconnect($this->server, $this->user, $sqlpassword); + } + else + { + if (!function_exists('odbc_connect')) + { + $this->connect_error = 'odbc_connect function does not exist, is odbc extension installed?'; + return $this->sql_error(''); + } + $this->db_connect_id = @odbc_connect($this->server, $this->user, $sqlpassword); + } + + return ($this->db_connect_id) ? $this->db_connect_id : $this->sql_error(''); + } + + /** + * Version information about used database + * @param bool $raw if true, only return the fetched sql_server_version + * @param bool $use_cache If true, it is safe to retrieve the value from the cache + * @return string sql server version + */ + function sql_server_info($raw = false, $use_cache = true) + { + global $cache; + + if (!$use_cache || empty($cache) || ($this->sql_server_version = $cache->get('mssqlodbc_version')) === false) + { + $result_id = @odbc_exec($this->db_connect_id, "SELECT SERVERPROPERTY('productversion'), SERVERPROPERTY('productlevel'), SERVERPROPERTY('edition')"); + + $row = false; + if ($result_id) + { + $row = @odbc_fetch_array($result_id); + @odbc_free_result($result_id); + } + + $this->sql_server_version = ($row) ? trim(implode(' ', $row)) : 0; + + if (!empty($cache) && $use_cache) + { + $cache->put('mssqlodbc_version', $this->sql_server_version); + } + } + + if ($raw) + { + return $this->sql_server_version; + } + + return ($this->sql_server_version) ? 'MSSQL (ODBC)
' . $this->sql_server_version : 'MSSQL (ODBC)'; + } + + /** + * SQL Transaction + * @access private + */ + function _sql_transaction($status = 'begin') + { + switch ($status) + { + case 'begin': + return @odbc_exec($this->db_connect_id, 'BEGIN TRANSACTION'); + break; + + case 'commit': + return @odbc_exec($this->db_connect_id, 'COMMIT TRANSACTION'); + break; + + case 'rollback': + return @odbc_exec($this->db_connect_id, 'ROLLBACK TRANSACTION'); + break; + } + + return true; + } + + /** + * Base query method + * + * @param string $query Contains the SQL query which shall be executed + * @param int $cache_ttl Either 0 to avoid caching or the time in seconds which the result shall be kept in cache + * @return mixed When casted to bool the returned value returns true on success and false on failure + * + * @access public + */ + function sql_query($query = '', $cache_ttl = 0) + { + if ($query != '') + { + global $cache; + + // EXPLAIN only in extra debug mode + if (defined('DEBUG')) + { + $this->sql_report('start', $query); + } + + $this->last_query_text = $query; + $this->query_result = ($cache && $cache_ttl) ? $cache->sql_load($query) : false; + $this->sql_add_num_queries($this->query_result); + + if ($this->query_result === false) + { + if (($this->query_result = @odbc_exec($this->db_connect_id, $query)) === false) + { + $this->sql_error($query); + } + + if (defined('DEBUG')) + { + $this->sql_report('stop', $query); + } + + if ($cache && $cache_ttl) + { + $this->open_queries[(int) $this->query_result] = $this->query_result; + $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); + } + else if (strpos($query, 'SELECT') === 0 && $this->query_result) + { + $this->open_queries[(int) $this->query_result] = $this->query_result; + } + } + else if (defined('DEBUG')) + { + $this->sql_report('fromcache', $query); + } + } + else + { + return false; + } + + return $this->query_result; + } + + /** + * Build LIMIT query + */ + function _sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) + { + $this->query_result = false; + + // Since TOP is only returning a set number of rows we won't need it if total is set to 0 (return all rows) + if ($total) + { + // We need to grab the total number of rows + the offset number of rows to get the correct result + if (strpos($query, 'SELECT DISTINCT') === 0) + { + $query = 'SELECT DISTINCT TOP ' . ($total + $offset) . ' ' . substr($query, 15); + } + else + { + $query = 'SELECT TOP ' . ($total + $offset) . ' ' . substr($query, 6); + } + } + + $result = $this->sql_query($query, $cache_ttl); + + // Seek by $offset rows + if ($offset) + { + $this->sql_rowseek($offset, $result); + } + + return $result; + } + + /** + * Return number of affected rows + */ + function sql_affectedrows() + { + return ($this->db_connect_id) ? @odbc_num_rows($this->query_result) : false; + } + + /** + * Fetch current row + * @note number of bytes returned depends on odbc.defaultlrl php.ini setting. If it is limited to 4K for example only 4K of data is returned max. + */ + function sql_fetchrow($query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($cache && $cache->sql_exists($query_id)) + { + return $cache->sql_fetchrow($query_id); + } + + return ($query_id !== false) ? @odbc_fetch_array($query_id) : false; + } + + /** + * Get last inserted id after insert statement + */ + function sql_nextid() + { + $result_id = @odbc_exec($this->db_connect_id, 'SELECT @@IDENTITY'); + + if ($result_id) + { + if (@odbc_fetch_array($result_id)) + { + $id = @odbc_result($result_id, 1); + @odbc_free_result($result_id); + return $id; + } + @odbc_free_result($result_id); + } + + return false; + } + + /** + * Free sql result + */ + function sql_freeresult($query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($cache && $cache->sql_exists($query_id)) + { + return $cache->sql_freeresult($query_id); + } + + if (isset($this->open_queries[(int) $query_id])) + { + unset($this->open_queries[(int) $query_id]); + return @odbc_free_result($query_id); + } + + return false; + } + + /** + * return sql error array + * @access private + */ + function _sql_error() + { + if (function_exists('odbc_errormsg')) + { + $error = array( + 'message' => @odbc_errormsg(), + 'code' => @odbc_error(), + ); + } + else + { + $error = array( + 'message' => $this->connect_error, + 'code' => '', + ); + } + + return $error; + } + + /** + * Close sql connection + * @access private + */ + function _sql_close() + { + return @odbc_close($this->db_connect_id); + } + + /** + * Build db-specific report + * @access private + */ + function _sql_report($mode, $query = '') + { + switch ($mode) + { + case 'start': + break; + + case 'fromcache': + $endtime = explode(' ', microtime()); + $endtime = $endtime[0] + $endtime[1]; + + $result = @odbc_exec($this->db_connect_id, $query); + while ($void = @odbc_fetch_array($result)) + { + // Take the time spent on parsing rows into account + } + @odbc_free_result($result); + + $splittime = explode(' ', microtime()); + $splittime = $splittime[0] + $splittime[1]; + + $this->sql_report('record_fromcache', $query, $endtime, $splittime); + + break; + } + } +} diff --git a/phpBB/phpbb/db/driver/mssqlnative.php b/phpBB/phpbb/db/driver/mssqlnative.php new file mode 100644 index 0000000000..28fc88298a --- /dev/null +++ b/phpBB/phpbb/db/driver/mssqlnative.php @@ -0,0 +1,613 @@ +m_cursor = 0; + $this->m_rows = array(); + $this->m_num_fields = sqlsrv_num_fields($queryresult); + $this->m_field_meta = sqlsrv_field_metadata($queryresult); + + while ($row = sqlsrv_fetch_array($queryresult, SQLSRV_FETCH_ASSOC)) + { + if ($row !== null) + { + foreach($row as $k => $v) + { + if (is_object($v) && method_exists($v, 'format')) + { + $row[$k] = $v->format("Y-m-d\TH:i:s\Z"); + } + } + $this->m_rows[] = $row;//read results into memory, cursors are not supported + } + } + + $this->m_row_count = sizeof($this->m_rows); + } + + private function array_to_obj($array, &$obj) + { + foreach ($array as $key => $value) + { + if (is_array($value)) + { + $obj->$key = new stdClass(); + array_to_obj($value, $obj->$key); + } + else + { + $obj->$key = $value; + } + } + return $obj; + } + + public function fetch($mode = SQLSRV_FETCH_BOTH, $object_class = 'stdClass') + { + if ($this->m_cursor >= $this->m_row_count || $this->m_row_count == 0) + { + return false; + } + + $ret = false; + $arr_num = array(); + + if ($mode == SQLSRV_FETCH_NUMERIC || $mode == SQLSRV_FETCH_BOTH) + { + foreach($this->m_rows[$this->m_cursor] as $key => $value) + { + $arr_num[] = $value; + } + } + + switch ($mode) + { + case SQLSRV_FETCH_ASSOC: + $ret = $this->m_rows[$this->m_cursor]; + break; + case SQLSRV_FETCH_NUMERIC: + $ret = $arr_num; + break; + case 'OBJECT': + $ret = $this->array_to_obj($this->m_rows[$this->m_cursor], $o = new $object_class); + break; + case SQLSRV_FETCH_BOTH: + default: + $ret = $this->m_rows[$this->m_cursor] + $arr_num; + break; + } + $this->m_cursor++; + return $ret; + } + + public function get($pos, $fld) + { + return $this->m_rows[$pos][$fld]; + } + + public function num_rows() + { + return $this->m_row_count; + } + + public function seek($iRow) + { + $this->m_cursor = min($iRow, $this->m_row_count); + } + + public function num_fields() + { + return $this->m_num_fields; + } + + public function field_name($nr) + { + $arr_keys = array_keys($this->m_rows[0]); + return $arr_keys[$nr]; + } + + public function field_type($nr) + { + $i = 0; + $int_type = -1; + $str_type = ''; + + foreach ($this->m_field_meta as $meta) + { + if ($nr == $i) + { + $int_type = $meta['Type']; + break; + } + $i++; + } + + //http://msdn.microsoft.com/en-us/library/cc296183.aspx contains type table + switch ($int_type) + { + case SQLSRV_SQLTYPE_BIGINT: $str_type = 'bigint'; break; + case SQLSRV_SQLTYPE_BINARY: $str_type = 'binary'; break; + case SQLSRV_SQLTYPE_BIT: $str_type = 'bit'; break; + case SQLSRV_SQLTYPE_CHAR: $str_type = 'char'; break; + case SQLSRV_SQLTYPE_DATETIME: $str_type = 'datetime'; break; + case SQLSRV_SQLTYPE_DECIMAL/*($precision, $scale)*/: $str_type = 'decimal'; break; + case SQLSRV_SQLTYPE_FLOAT: $str_type = 'float'; break; + case SQLSRV_SQLTYPE_IMAGE: $str_type = 'image'; break; + case SQLSRV_SQLTYPE_INT: $str_type = 'int'; break; + case SQLSRV_SQLTYPE_MONEY: $str_type = 'money'; break; + case SQLSRV_SQLTYPE_NCHAR/*($charCount)*/: $str_type = 'nchar'; break; + case SQLSRV_SQLTYPE_NUMERIC/*($precision, $scale)*/: $str_type = 'numeric'; break; + case SQLSRV_SQLTYPE_NVARCHAR/*($charCount)*/: $str_type = 'nvarchar'; break; + case SQLSRV_SQLTYPE_NTEXT: $str_type = 'ntext'; break; + case SQLSRV_SQLTYPE_REAL: $str_type = 'real'; break; + case SQLSRV_SQLTYPE_SMALLDATETIME: $str_type = 'smalldatetime'; break; + case SQLSRV_SQLTYPE_SMALLINT: $str_type = 'smallint'; break; + case SQLSRV_SQLTYPE_SMALLMONEY: $str_type = 'smallmoney'; break; + case SQLSRV_SQLTYPE_TEXT: $str_type = 'text'; break; + case SQLSRV_SQLTYPE_TIMESTAMP: $str_type = 'timestamp'; break; + case SQLSRV_SQLTYPE_TINYINT: $str_type = 'tinyint'; break; + case SQLSRV_SQLTYPE_UNIQUEIDENTIFIER: $str_type = 'uniqueidentifier'; break; + case SQLSRV_SQLTYPE_UDT: $str_type = 'UDT'; break; + case SQLSRV_SQLTYPE_VARBINARY/*($byteCount)*/: $str_type = 'varbinary'; break; + case SQLSRV_SQLTYPE_VARCHAR/*($charCount)*/: $str_type = 'varchar'; break; + case SQLSRV_SQLTYPE_XML: $str_type = 'xml'; break; + default: $str_type = $int_type; + } + return $str_type; + } + + public function free() + { + unset($this->m_rows); + return; + } +} + +/** +* @package dbal +*/ +class phpbb_db_driver_mssqlnative extends phpbb_db_driver_mssql_base +{ + var $m_insert_id = NULL; + var $last_query_text = ''; + var $query_options = array(); + var $connect_error = ''; + + /** + * Connect to server + */ + function sql_connect($sqlserver, $sqluser, $sqlpassword, $database, $port = false, $persistency = false, $new_link = false) + { + // Test for driver support, to avoid suppressed fatal error + if (!function_exists('sqlsrv_connect')) + { + $this->connect_error = 'Native MS SQL Server driver for PHP is missing or needs to be updated. Version 1.1 or later is required to install phpBB3. You can download the driver from: http://www.microsoft.com/sqlserver/2005/en/us/PHP-Driver.aspx'; + return $this->sql_error(''); + } + + //set up connection variables + $this->persistency = $persistency; + $this->user = $sqluser; + $this->dbname = $database; + $port_delimiter = (defined('PHP_OS') && substr(PHP_OS, 0, 3) === 'WIN') ? ',' : ':'; + $this->server = $sqlserver . (($port) ? $port_delimiter . $port : ''); + + //connect to database + $this->db_connect_id = sqlsrv_connect($this->server, array( + 'Database' => $this->dbname, + 'UID' => $this->user, + 'PWD' => $sqlpassword + )); + + return ($this->db_connect_id) ? $this->db_connect_id : $this->sql_error(''); + } + + /** + * Version information about used database + * @param bool $raw if true, only return the fetched sql_server_version + * @param bool $use_cache If true, it is safe to retrieve the value from the cache + * @return string sql server version + */ + function sql_server_info($raw = false, $use_cache = true) + { + global $cache; + + if (!$use_cache || empty($cache) || ($this->sql_server_version = $cache->get('mssql_version')) === false) + { + $arr_server_info = sqlsrv_server_info($this->db_connect_id); + $this->sql_server_version = $arr_server_info['SQLServerVersion']; + + if (!empty($cache) && $use_cache) + { + $cache->put('mssql_version', $this->sql_server_version); + } + } + + if ($raw) + { + return $this->sql_server_version; + } + + return ($this->sql_server_version) ? 'MSSQL
' . $this->sql_server_version : 'MSSQL'; + } + + /** + * {@inheritDoc} + */ + function sql_buffer_nested_transactions() + { + return true; + } + + /** + * SQL Transaction + * @access private + */ + function _sql_transaction($status = 'begin') + { + switch ($status) + { + case 'begin': + return sqlsrv_begin_transaction($this->db_connect_id); + break; + + case 'commit': + return sqlsrv_commit($this->db_connect_id); + break; + + case 'rollback': + return sqlsrv_rollback($this->db_connect_id); + break; + } + return true; + } + + /** + * Base query method + * + * @param string $query Contains the SQL query which shall be executed + * @param int $cache_ttl Either 0 to avoid caching or the time in seconds which the result shall be kept in cache + * @return mixed When casted to bool the returned value returns true on success and false on failure + * + * @access public + */ + function sql_query($query = '', $cache_ttl = 0) + { + if ($query != '') + { + global $cache; + + // EXPLAIN only in extra debug mode + if (defined('DEBUG')) + { + $this->sql_report('start', $query); + } + + $this->last_query_text = $query; + $this->query_result = ($cache && $cache_ttl) ? $cache->sql_load($query) : false; + $this->sql_add_num_queries($this->query_result); + + if ($this->query_result === false) + { + if (($this->query_result = @sqlsrv_query($this->db_connect_id, $query, array(), $this->query_options)) === false) + { + $this->sql_error($query); + } + // reset options for next query + $this->query_options = array(); + + if (defined('DEBUG')) + { + $this->sql_report('stop', $query); + } + + if ($cache && $cache_ttl) + { + $this->open_queries[(int) $this->query_result] = $this->query_result; + $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); + } + else if (strpos($query, 'SELECT') === 0 && $this->query_result) + { + $this->open_queries[(int) $this->query_result] = $this->query_result; + } + } + else if (defined('DEBUG')) + { + $this->sql_report('fromcache', $query); + } + } + else + { + return false; + } + return $this->query_result; + } + + /** + * Build LIMIT query + */ + function _sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) + { + $this->query_result = false; + + // total == 0 means all results - not zero results + if ($offset == 0 && $total !== 0) + { + if (strpos($query, "SELECT") === false) + { + $query = "TOP {$total} " . $query; + } + else + { + $query = preg_replace('/SELECT(\s*DISTINCT)?/Dsi', 'SELECT$1 TOP '.$total, $query); + } + } + else if ($offset > 0) + { + $query = preg_replace('/SELECT(\s*DISTINCT)?/Dsi', 'SELECT$1 TOP(10000000) ', $query); + $query = 'SELECT * + FROM (SELECT sub2.*, ROW_NUMBER() OVER(ORDER BY sub2.line2) AS line3 + FROM (SELECT 1 AS line2, sub1.* FROM (' . $query . ') AS sub1) as sub2) AS sub3'; + + if ($total > 0) + { + $query .= ' WHERE line3 BETWEEN ' . ($offset+1) . ' AND ' . ($offset + $total); + } + else + { + $query .= ' WHERE line3 > ' . $offset; + } + } + + $result = $this->sql_query($query, $cache_ttl); + + return $result; + } + + /** + * Return number of affected rows + */ + function sql_affectedrows() + { + return ($this->db_connect_id) ? @sqlsrv_rows_affected($this->query_result) : false; + } + + /** + * Fetch current row + */ + function sql_fetchrow($query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($cache && $cache->sql_exists($query_id)) + { + return $cache->sql_fetchrow($query_id); + } + + if ($query_id === false) + { + return false; + } + + $row = @sqlsrv_fetch_array($query_id, SQLSRV_FETCH_ASSOC); + + if ($row) + { + foreach ($row as $key => $value) + { + $row[$key] = ($value === ' ' || $value === NULL) ? '' : $value; + } + + // remove helper values from LIMIT queries + if (isset($row['line2'])) + { + unset($row['line2'], $row['line3']); + } + } + return (sizeof($row)) ? $row : false; + } + + /** + * Get last inserted id after insert statement + */ + function sql_nextid() + { + $result_id = @sqlsrv_query($this->db_connect_id, 'SELECT @@IDENTITY'); + + if ($result_id !== false) + { + $row = @sqlsrv_fetch_array($result_id); + $id = $row[0]; + @sqlsrv_free_stmt($result_id); + return $id; + } + else + { + return false; + } + } + + /** + * Free sql result + */ + function sql_freeresult($query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($cache->sql_exists($query_id)) + { + return $cache->sql_freeresult($query_id); + } + + if (isset($this->open_queries[(int) $query_id])) + { + unset($this->open_queries[(int) $query_id]); + return @sqlsrv_free_stmt($query_id); + } + return false; + } + + /** + * return sql error array + * @access private + */ + function _sql_error() + { + if (function_exists('sqlsrv_errors')) + { + $errors = @sqlsrv_errors(SQLSRV_ERR_ERRORS); + $error_message = ''; + $code = 0; + + if ($errors != null) + { + foreach ($errors as $error) + { + $error_message .= "SQLSTATE: " . $error[ 'SQLSTATE'] . "\n"; + $error_message .= "code: " . $error[ 'code'] . "\n"; + $code = $error['code']; + $error_message .= "message: " . $error[ 'message'] . "\n"; + } + $this->last_error_result = $error_message; + $error = $this->last_error_result; + } + else + { + $error = (isset($this->last_error_result) && $this->last_error_result) ? $this->last_error_result : array(); + } + + $error = array( + 'message' => $error, + 'code' => $code, + ); + } + else + { + $error = array( + 'message' => $this->connect_error, + 'code' => '', + ); + } + + return $error; + } + + /** + * Close sql connection + * @access private + */ + function _sql_close() + { + return @sqlsrv_close($this->db_connect_id); + } + + /** + * Build db-specific report + * @access private + */ + function _sql_report($mode, $query = '') + { + switch ($mode) + { + case 'start': + $html_table = false; + @sqlsrv_query($this->db_connect_id, 'SET SHOWPLAN_TEXT ON;'); + if ($result = @sqlsrv_query($this->db_connect_id, $query)) + { + @sqlsrv_next_result($result); + while ($row = @sqlsrv_fetch_array($result)) + { + $html_table = $this->sql_report('add_select_row', $query, $html_table, $row); + } + } + @sqlsrv_query($this->db_connect_id, 'SET SHOWPLAN_TEXT OFF;'); + @sqlsrv_free_stmt($result); + + if ($html_table) + { + $this->html_hold .= ''; + } + break; + + case 'fromcache': + $endtime = explode(' ', microtime()); + $endtime = $endtime[0] + $endtime[1]; + + $result = @sqlsrv_query($this->db_connect_id, $query); + while ($void = @sqlsrv_fetch_array($result)) + { + // Take the time spent on parsing rows into account + } + @sqlsrv_free_stmt($result); + + $splittime = explode(' ', microtime()); + $splittime = $splittime[0] + $splittime[1]; + + $this->sql_report('record_fromcache', $query, $endtime, $splittime); + + break; + } + } + + /** + * Utility method used to retrieve number of rows + * Emulates mysql_num_rows + * Used in acp_database.php -> write_data_mssqlnative() + * Requires a static or keyset cursor to be definde via + * mssqlnative_set_query_options() + */ + function mssqlnative_num_rows($res) + { + if ($res !== false) + { + return sqlsrv_num_rows($res); + } + else + { + return false; + } + } + + /** + * Allows setting mssqlnative specific query options passed to sqlsrv_query as 4th parameter. + */ + function mssqlnative_set_query_options($options) + { + $this->query_options = $options; + } +} diff --git a/phpBB/phpbb/db/driver/mysql.php b/phpBB/phpbb/db/driver/mysql.php new file mode 100644 index 0000000000..f3744ac09d --- /dev/null +++ b/phpBB/phpbb/db/driver/mysql.php @@ -0,0 +1,472 @@ +persistency = $persistency; + $this->user = $sqluser; + $this->server = $sqlserver . (($port) ? ':' . $port : ''); + $this->dbname = $database; + + $this->sql_layer = 'mysql4'; + + if ($this->persistency) + { + if (!function_exists('mysql_pconnect')) + { + $this->connect_error = 'mysql_pconnect function does not exist, is mysql extension installed?'; + return $this->sql_error(''); + } + $this->db_connect_id = @mysql_pconnect($this->server, $this->user, $sqlpassword); + } + else + { + if (!function_exists('mysql_connect')) + { + $this->connect_error = 'mysql_connect function does not exist, is mysql extension installed?'; + return $this->sql_error(''); + } + $this->db_connect_id = @mysql_connect($this->server, $this->user, $sqlpassword, $new_link); + } + + if ($this->db_connect_id && $this->dbname != '') + { + if (@mysql_select_db($this->dbname, $this->db_connect_id)) + { + // Determine what version we are using and if it natively supports UNICODE + if (version_compare($this->sql_server_info(true), '4.1.0', '>=')) + { + @mysql_query("SET NAMES 'utf8'", $this->db_connect_id); + + // enforce strict mode on databases that support it + if (version_compare($this->sql_server_info(true), '5.0.2', '>=')) + { + $result = @mysql_query('SELECT @@session.sql_mode AS sql_mode', $this->db_connect_id); + $row = @mysql_fetch_assoc($result); + @mysql_free_result($result); + $modes = array_map('trim', explode(',', $row['sql_mode'])); + + // TRADITIONAL includes STRICT_ALL_TABLES and STRICT_TRANS_TABLES + if (!in_array('TRADITIONAL', $modes)) + { + if (!in_array('STRICT_ALL_TABLES', $modes)) + { + $modes[] = 'STRICT_ALL_TABLES'; + } + + if (!in_array('STRICT_TRANS_TABLES', $modes)) + { + $modes[] = 'STRICT_TRANS_TABLES'; + } + } + + $mode = implode(',', $modes); + @mysql_query("SET SESSION sql_mode='{$mode}'", $this->db_connect_id); + } + } + else if (version_compare($this->sql_server_info(true), '4.0.0', '<')) + { + $this->sql_layer = 'mysql'; + } + + return $this->db_connect_id; + } + } + + return $this->sql_error(''); + } + + /** + * Version information about used database + * @param bool $raw if true, only return the fetched sql_server_version + * @param bool $use_cache If true, it is safe to retrieve the value from the cache + * @return string sql server version + */ + function sql_server_info($raw = false, $use_cache = true) + { + global $cache; + + if (!$use_cache || empty($cache) || ($this->sql_server_version = $cache->get('mysql_version')) === false) + { + $result = @mysql_query('SELECT VERSION() AS version', $this->db_connect_id); + $row = @mysql_fetch_assoc($result); + @mysql_free_result($result); + + $this->sql_server_version = $row['version']; + + if (!empty($cache) && $use_cache) + { + $cache->put('mysql_version', $this->sql_server_version); + } + } + + return ($raw) ? $this->sql_server_version : 'MySQL ' . $this->sql_server_version; + } + + /** + * SQL Transaction + * @access private + */ + function _sql_transaction($status = 'begin') + { + switch ($status) + { + case 'begin': + return @mysql_query('BEGIN', $this->db_connect_id); + break; + + case 'commit': + return @mysql_query('COMMIT', $this->db_connect_id); + break; + + case 'rollback': + return @mysql_query('ROLLBACK', $this->db_connect_id); + break; + } + + return true; + } + + /** + * Base query method + * + * @param string $query Contains the SQL query which shall be executed + * @param int $cache_ttl Either 0 to avoid caching or the time in seconds which the result shall be kept in cache + * @return mixed When casted to bool the returned value returns true on success and false on failure + * + * @access public + */ + function sql_query($query = '', $cache_ttl = 0) + { + if ($query != '') + { + global $cache; + + // EXPLAIN only in extra debug mode + if (defined('DEBUG')) + { + $this->sql_report('start', $query); + } + + $this->query_result = ($cache && $cache_ttl) ? $cache->sql_load($query) : false; + $this->sql_add_num_queries($this->query_result); + + if ($this->query_result === false) + { + if (($this->query_result = @mysql_query($query, $this->db_connect_id)) === false) + { + $this->sql_error($query); + } + + if (defined('DEBUG')) + { + $this->sql_report('stop', $query); + } + + if ($cache && $cache_ttl) + { + $this->open_queries[(int) $this->query_result] = $this->query_result; + $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); + } + else if (strpos($query, 'SELECT') === 0 && $this->query_result) + { + $this->open_queries[(int) $this->query_result] = $this->query_result; + } + } + else if (defined('DEBUG')) + { + $this->sql_report('fromcache', $query); + } + } + else + { + return false; + } + + return $this->query_result; + } + + /** + * Return number of affected rows + */ + function sql_affectedrows() + { + return ($this->db_connect_id) ? @mysql_affected_rows($this->db_connect_id) : false; + } + + /** + * Fetch current row + */ + function sql_fetchrow($query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($cache && $cache->sql_exists($query_id)) + { + return $cache->sql_fetchrow($query_id); + } + + return ($query_id !== false) ? @mysql_fetch_assoc($query_id) : false; + } + + /** + * Seek to given row number + * rownum is zero-based + */ + function sql_rowseek($rownum, &$query_id) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($cache && $cache->sql_exists($query_id)) + { + return $cache->sql_rowseek($rownum, $query_id); + } + + return ($query_id !== false) ? @mysql_data_seek($query_id, $rownum) : false; + } + + /** + * Get last inserted id after insert statement + */ + function sql_nextid() + { + return ($this->db_connect_id) ? @mysql_insert_id($this->db_connect_id) : false; + } + + /** + * Free sql result + */ + function sql_freeresult($query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($cache && $cache->sql_exists($query_id)) + { + return $cache->sql_freeresult($query_id); + } + + if (isset($this->open_queries[(int) $query_id])) + { + unset($this->open_queries[(int) $query_id]); + return @mysql_free_result($query_id); + } + + return false; + } + + /** + * Escape string used in sql query + */ + function sql_escape($msg) + { + if (!$this->db_connect_id) + { + return @mysql_real_escape_string($msg); + } + + return @mysql_real_escape_string($msg, $this->db_connect_id); + } + + /** + * return sql error array + * @access private + */ + function _sql_error() + { + if ($this->db_connect_id) + { + $error = array( + 'message' => @mysql_error($this->db_connect_id), + 'code' => @mysql_errno($this->db_connect_id), + ); + } + else if (function_exists('mysql_error')) + { + $error = array( + 'message' => @mysql_error(), + 'code' => @mysql_errno(), + ); + } + else + { + $error = array( + 'message' => $this->connect_error, + 'code' => '', + ); + } + + return $error; + } + + /** + * Close sql connection + * @access private + */ + function _sql_close() + { + return @mysql_close($this->db_connect_id); + } + + /** + * Build db-specific report + * @access private + */ + function _sql_report($mode, $query = '') + { + static $test_prof; + + // current detection method, might just switch to see the existance of INFORMATION_SCHEMA.PROFILING + if ($test_prof === null) + { + $test_prof = false; + if (version_compare($this->sql_server_info(true), '5.0.37', '>=') && version_compare($this->sql_server_info(true), '5.1', '<')) + { + $test_prof = true; + } + } + + switch ($mode) + { + case 'start': + + $explain_query = $query; + if (preg_match('/UPDATE ([a-z0-9_]+).*?WHERE(.*)/s', $query, $m)) + { + $explain_query = 'SELECT * FROM ' . $m[1] . ' WHERE ' . $m[2]; + } + else if (preg_match('/DELETE FROM ([a-z0-9_]+).*?WHERE(.*)/s', $query, $m)) + { + $explain_query = 'SELECT * FROM ' . $m[1] . ' WHERE ' . $m[2]; + } + + if (preg_match('/^SELECT/', $explain_query)) + { + $html_table = false; + + // begin profiling + if ($test_prof) + { + @mysql_query('SET profiling = 1;', $this->db_connect_id); + } + + if ($result = @mysql_query("EXPLAIN $explain_query", $this->db_connect_id)) + { + while ($row = @mysql_fetch_assoc($result)) + { + $html_table = $this->sql_report('add_select_row', $query, $html_table, $row); + } + } + @mysql_free_result($result); + + if ($html_table) + { + $this->html_hold .= ''; + } + + if ($test_prof) + { + $html_table = false; + + // get the last profile + if ($result = @mysql_query('SHOW PROFILE ALL;', $this->db_connect_id)) + { + $this->html_hold .= '
'; + while ($row = @mysql_fetch_assoc($result)) + { + // make HTML safe + if (!empty($row['Source_function'])) + { + $row['Source_function'] = str_replace(array('<', '>'), array('<', '>'), $row['Source_function']); + } + + // remove unsupported features + foreach ($row as $key => $val) + { + if ($val === null) + { + unset($row[$key]); + } + } + $html_table = $this->sql_report('add_select_row', $query, $html_table, $row); + } + } + @mysql_free_result($result); + + if ($html_table) + { + $this->html_hold .= ''; + } + + @mysql_query('SET profiling = 0;', $this->db_connect_id); + } + } + + break; + + case 'fromcache': + $endtime = explode(' ', microtime()); + $endtime = $endtime[0] + $endtime[1]; + + $result = @mysql_query($query, $this->db_connect_id); + while ($void = @mysql_fetch_assoc($result)) + { + // Take the time spent on parsing rows into account + } + @mysql_free_result($result); + + $splittime = explode(' ', microtime()); + $splittime = $splittime[0] + $splittime[1]; + + $this->sql_report('record_fromcache', $query, $endtime, $splittime); + + break; + } + } +} diff --git a/phpBB/phpbb/db/driver/mysql_base.php b/phpBB/phpbb/db/driver/mysql_base.php new file mode 100644 index 0000000000..ba44ea61aa --- /dev/null +++ b/phpBB/phpbb/db/driver/mysql_base.php @@ -0,0 +1,145 @@ +query_result = false; + + // if $total is set to 0 we do not want to limit the number of rows + if ($total == 0) + { + // MySQL 4.1+ no longer supports -1 in limit queries + $total = '18446744073709551615'; + } + + $query .= "\n LIMIT " . ((!empty($offset)) ? $offset . ', ' . $total : $total); + + return $this->sql_query($query, $cache_ttl); + } + + /** + * Gets the estimated number of rows in a specified table. + * + * @param string $table_name Table name + * + * @return string Number of rows in $table_name. + * Prefixed with ~ if estimated (otherwise exact). + * + * @access public + */ + function get_estimated_row_count($table_name) + { + $table_status = $this->get_table_status($table_name); + + if (isset($table_status['Engine'])) + { + if ($table_status['Engine'] === 'MyISAM') + { + return $table_status['Rows']; + } + else if ($table_status['Engine'] === 'InnoDB' && $table_status['Rows'] > 100000) + { + return '~' . $table_status['Rows']; + } + } + + return parent::get_row_count($table_name); + } + + /** + * Gets the exact number of rows in a specified table. + * + * @param string $table_name Table name + * + * @return string Exact number of rows in $table_name. + * + * @access public + */ + function get_row_count($table_name) + { + $table_status = $this->get_table_status($table_name); + + if (isset($table_status['Engine']) && $table_status['Engine'] === 'MyISAM') + { + return $table_status['Rows']; + } + + return parent::get_row_count($table_name); + } + + /** + * Gets some information about the specified table. + * + * @param string $table_name Table name + * + * @return array + * + * @access protected + */ + function get_table_status($table_name) + { + $sql = "SHOW TABLE STATUS + LIKE '" . $this->sql_escape($table_name) . "'"; + $result = $this->sql_query($sql); + $table_status = $this->sql_fetchrow($result); + $this->sql_freeresult($result); + + return $table_status; + } + + /** + * Build LIKE expression + * @access private + */ + function _sql_like_expression($expression) + { + return $expression; + } + + /** + * Build db-specific query data + * @access private + */ + function _sql_custom_build($stage, $data) + { + switch ($stage) + { + case 'FROM': + $data = '(' . $data . ')'; + break; + } + + return $data; + } +} diff --git a/phpBB/phpbb/db/driver/mysqli.php b/phpBB/phpbb/db/driver/mysqli.php new file mode 100644 index 0000000000..0f7a73ee6e --- /dev/null +++ b/phpBB/phpbb/db/driver/mysqli.php @@ -0,0 +1,463 @@ +connect_error = 'mysqli_connect function does not exist, is mysqli extension installed?'; + return $this->sql_error(''); + } + + // Mysqli extension supports persistent connection since PHP 5.3.0 + $this->persistency = (version_compare(PHP_VERSION, '5.3.0', '>=')) ? $persistency : false; + $this->user = $sqluser; + + // If persistent connection, set dbhost to localhost when empty and prepend it with 'p:' prefix + $this->server = ($this->persistency) ? 'p:' . (($sqlserver) ? $sqlserver : 'localhost') : $sqlserver; + + $this->dbname = $database; + $port = (!$port) ? NULL : $port; + + // If port is set and it is not numeric, most likely mysqli socket is set. + // Try to map it to the $socket parameter. + $socket = NULL; + if ($port) + { + if (is_numeric($port)) + { + $port = (int) $port; + } + else + { + $socket = $port; + $port = NULL; + } + } + + $this->db_connect_id = @mysqli_connect($this->server, $this->user, $sqlpassword, $this->dbname, $port, $socket); + + if ($this->db_connect_id && $this->dbname != '') + { + @mysqli_query($this->db_connect_id, "SET NAMES 'utf8'"); + + // enforce strict mode on databases that support it + if (version_compare($this->sql_server_info(true), '5.0.2', '>=')) + { + $result = @mysqli_query($this->db_connect_id, 'SELECT @@session.sql_mode AS sql_mode'); + $row = @mysqli_fetch_assoc($result); + @mysqli_free_result($result); + + $modes = array_map('trim', explode(',', $row['sql_mode'])); + + // TRADITIONAL includes STRICT_ALL_TABLES and STRICT_TRANS_TABLES + if (!in_array('TRADITIONAL', $modes)) + { + if (!in_array('STRICT_ALL_TABLES', $modes)) + { + $modes[] = 'STRICT_ALL_TABLES'; + } + + if (!in_array('STRICT_TRANS_TABLES', $modes)) + { + $modes[] = 'STRICT_TRANS_TABLES'; + } + } + + $mode = implode(',', $modes); + @mysqli_query($this->db_connect_id, "SET SESSION sql_mode='{$mode}'"); + } + return $this->db_connect_id; + } + + return $this->sql_error(''); + } + + /** + * Version information about used database + * @param bool $raw if true, only return the fetched sql_server_version + * @param bool $use_cache If true, it is safe to retrieve the value from the cache + * @return string sql server version + */ + function sql_server_info($raw = false, $use_cache = true) + { + global $cache; + + if (!$use_cache || empty($cache) || ($this->sql_server_version = $cache->get('mysqli_version')) === false) + { + $result = @mysqli_query($this->db_connect_id, 'SELECT VERSION() AS version'); + $row = @mysqli_fetch_assoc($result); + @mysqli_free_result($result); + + $this->sql_server_version = $row['version']; + + if (!empty($cache) && $use_cache) + { + $cache->put('mysqli_version', $this->sql_server_version); + } + } + + return ($raw) ? $this->sql_server_version : 'MySQL(i) ' . $this->sql_server_version; + } + + /** + * SQL Transaction + * @access private + */ + function _sql_transaction($status = 'begin') + { + switch ($status) + { + case 'begin': + return @mysqli_autocommit($this->db_connect_id, false); + break; + + case 'commit': + $result = @mysqli_commit($this->db_connect_id); + @mysqli_autocommit($this->db_connect_id, true); + return $result; + break; + + case 'rollback': + $result = @mysqli_rollback($this->db_connect_id); + @mysqli_autocommit($this->db_connect_id, true); + return $result; + break; + } + + return true; + } + + /** + * Base query method + * + * @param string $query Contains the SQL query which shall be executed + * @param int $cache_ttl Either 0 to avoid caching or the time in seconds which the result shall be kept in cache + * @return mixed When casted to bool the returned value returns true on success and false on failure + * + * @access public + */ + function sql_query($query = '', $cache_ttl = 0) + { + if ($query != '') + { + global $cache; + + // EXPLAIN only in extra debug mode + if (defined('DEBUG')) + { + $this->sql_report('start', $query); + } + + $this->query_result = ($cache && $cache_ttl) ? $cache->sql_load($query) : false; + $this->sql_add_num_queries($this->query_result); + + if ($this->query_result === false) + { + if (($this->query_result = @mysqli_query($this->db_connect_id, $query)) === false) + { + $this->sql_error($query); + } + + if (defined('DEBUG')) + { + $this->sql_report('stop', $query); + } + + if ($cache && $cache_ttl) + { + $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); + } + } + else if (defined('DEBUG')) + { + $this->sql_report('fromcache', $query); + } + } + else + { + return false; + } + + return $this->query_result; + } + + /** + * Return number of affected rows + */ + function sql_affectedrows() + { + return ($this->db_connect_id) ? @mysqli_affected_rows($this->db_connect_id) : false; + } + + /** + * Fetch current row + */ + function sql_fetchrow($query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($cache && !is_object($query_id) && $cache->sql_exists($query_id)) + { + return $cache->sql_fetchrow($query_id); + } + + if ($query_id !== false) + { + $result = @mysqli_fetch_assoc($query_id); + return $result !== null ? $result : false; + } + + return false; + } + + /** + * Seek to given row number + * rownum is zero-based + */ + function sql_rowseek($rownum, &$query_id) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($cache && !is_object($query_id) && $cache->sql_exists($query_id)) + { + return $cache->sql_rowseek($rownum, $query_id); + } + + return ($query_id !== false) ? @mysqli_data_seek($query_id, $rownum) : false; + } + + /** + * Get last inserted id after insert statement + */ + function sql_nextid() + { + return ($this->db_connect_id) ? @mysqli_insert_id($this->db_connect_id) : false; + } + + /** + * Free sql result + */ + function sql_freeresult($query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($cache && !is_object($query_id) && $cache->sql_exists($query_id)) + { + return $cache->sql_freeresult($query_id); + } + + return @mysqli_free_result($query_id); + } + + /** + * Escape string used in sql query + */ + function sql_escape($msg) + { + return @mysqli_real_escape_string($this->db_connect_id, $msg); + } + + /** + * return sql error array + * @access private + */ + function _sql_error() + { + if ($this->db_connect_id) + { + $error = array( + 'message' => @mysqli_error($this->db_connect_id), + 'code' => @mysqli_errno($this->db_connect_id) + ); + } + else if (function_exists('mysqli_connect_error')) + { + $error = array( + 'message' => @mysqli_connect_error(), + 'code' => @mysqli_connect_errno(), + ); + } + else + { + $error = array( + 'message' => $this->connect_error, + 'code' => '', + ); + } + + return $error; + } + + /** + * Close sql connection + * @access private + */ + function _sql_close() + { + return @mysqli_close($this->db_connect_id); + } + + /** + * Build db-specific report + * @access private + */ + function _sql_report($mode, $query = '') + { + static $test_prof; + + // current detection method, might just switch to see the existance of INFORMATION_SCHEMA.PROFILING + if ($test_prof === null) + { + $test_prof = false; + if (strpos(mysqli_get_server_info($this->db_connect_id), 'community') !== false) + { + $ver = mysqli_get_server_version($this->db_connect_id); + if ($ver >= 50037 && $ver < 50100) + { + $test_prof = true; + } + } + } + + switch ($mode) + { + case 'start': + + $explain_query = $query; + if (preg_match('/UPDATE ([a-z0-9_]+).*?WHERE(.*)/s', $query, $m)) + { + $explain_query = 'SELECT * FROM ' . $m[1] . ' WHERE ' . $m[2]; + } + else if (preg_match('/DELETE FROM ([a-z0-9_]+).*?WHERE(.*)/s', $query, $m)) + { + $explain_query = 'SELECT * FROM ' . $m[1] . ' WHERE ' . $m[2]; + } + + if (preg_match('/^SELECT/', $explain_query)) + { + $html_table = false; + + // begin profiling + if ($test_prof) + { + @mysqli_query($this->db_connect_id, 'SET profiling = 1;'); + } + + if ($result = @mysqli_query($this->db_connect_id, "EXPLAIN $explain_query")) + { + while ($row = @mysqli_fetch_assoc($result)) + { + $html_table = $this->sql_report('add_select_row', $query, $html_table, $row); + } + } + @mysqli_free_result($result); + + if ($html_table) + { + $this->html_hold .= ''; + } + + if ($test_prof) + { + $html_table = false; + + // get the last profile + if ($result = @mysqli_query($this->db_connect_id, 'SHOW PROFILE ALL;')) + { + $this->html_hold .= '
'; + while ($row = @mysqli_fetch_assoc($result)) + { + // make HTML safe + if (!empty($row['Source_function'])) + { + $row['Source_function'] = str_replace(array('<', '>'), array('<', '>'), $row['Source_function']); + } + + // remove unsupported features + foreach ($row as $key => $val) + { + if ($val === null) + { + unset($row[$key]); + } + } + $html_table = $this->sql_report('add_select_row', $query, $html_table, $row); + } + } + @mysqli_free_result($result); + + if ($html_table) + { + $this->html_hold .= ''; + } + + @mysqli_query($this->db_connect_id, 'SET profiling = 0;'); + } + } + + break; + + case 'fromcache': + $endtime = explode(' ', microtime()); + $endtime = $endtime[0] + $endtime[1]; + + $result = @mysqli_query($this->db_connect_id, $query); + while ($void = @mysqli_fetch_assoc($result)) + { + // Take the time spent on parsing rows into account + } + @mysqli_free_result($result); + + $splittime = explode(' ', microtime()); + $splittime = $splittime[0] + $splittime[1]; + + $this->sql_report('record_fromcache', $query, $endtime, $splittime); + + break; + } + } +} diff --git a/phpBB/phpbb/db/driver/oracle.php b/phpBB/phpbb/db/driver/oracle.php new file mode 100644 index 0000000000..e21e07055d --- /dev/null +++ b/phpBB/phpbb/db/driver/oracle.php @@ -0,0 +1,803 @@ +persistency = $persistency; + $this->user = $sqluser; + $this->server = $sqlserver . (($port) ? ':' . $port : ''); + $this->dbname = $database; + + $connect = $database; + + // support for "easy connect naming" + if ($sqlserver !== '' && $sqlserver !== '/') + { + if (substr($sqlserver, -1, 1) == '/') + { + $sqlserver == substr($sqlserver, 0, -1); + } + $connect = $sqlserver . (($port) ? ':' . $port : '') . '/' . $database; + } + + if ($new_link) + { + if (!function_exists('ocinlogon')) + { + $this->connect_error = 'ocinlogon function does not exist, is oci extension installed?'; + return $this->sql_error(''); + } + $this->db_connect_id = @ocinlogon($this->user, $sqlpassword, $connect, 'UTF8'); + } + else if ($this->persistency) + { + if (!function_exists('ociplogon')) + { + $this->connect_error = 'ociplogon function does not exist, is oci extension installed?'; + return $this->sql_error(''); + } + $this->db_connect_id = @ociplogon($this->user, $sqlpassword, $connect, 'UTF8'); + } + else + { + if (!function_exists('ocilogon')) + { + $this->connect_error = 'ocilogon function does not exist, is oci extension installed?'; + return $this->sql_error(''); + } + $this->db_connect_id = @ocilogon($this->user, $sqlpassword, $connect, 'UTF8'); + } + + return ($this->db_connect_id) ? $this->db_connect_id : $this->sql_error(''); + } + + /** + * Version information about used database + * @param bool $raw if true, only return the fetched sql_server_version + * @param bool $use_cache forced to false for Oracle + * @return string sql server version + */ + function sql_server_info($raw = false, $use_cache = true) + { + /** + * force $use_cache false. I didn't research why the caching code below is commented out + * but I assume its because the Oracle extension provides a direct method to access it + * without a query. + */ + + $use_cache = false; +/* + global $cache; + + if (empty($cache) || ($this->sql_server_version = $cache->get('oracle_version')) === false) + { + $result = @ociparse($this->db_connect_id, 'SELECT * FROM v$version WHERE banner LIKE \'Oracle%\''); + @ociexecute($result, OCI_DEFAULT); + @ocicommit($this->db_connect_id); + + $row = array(); + @ocifetchinto($result, $row, OCI_ASSOC + OCI_RETURN_NULLS); + @ocifreestatement($result); + $this->sql_server_version = trim($row['BANNER']); + + $cache->put('oracle_version', $this->sql_server_version); + } +*/ + $this->sql_server_version = @ociserverversion($this->db_connect_id); + + return $this->sql_server_version; + } + + /** + * SQL Transaction + * @access private + */ + function _sql_transaction($status = 'begin') + { + switch ($status) + { + case 'begin': + return true; + break; + + case 'commit': + return @ocicommit($this->db_connect_id); + break; + + case 'rollback': + return @ocirollback($this->db_connect_id); + break; + } + + return true; + } + + /** + * Oracle specific code to handle the fact that it does not compare columns properly + * @access private + */ + function _rewrite_col_compare($args) + { + if (sizeof($args) == 4) + { + if ($args[2] == '=') + { + return '(' . $args[0] . ' OR (' . $args[1] . ' is NULL AND ' . $args[3] . ' is NULL))'; + } + else if ($args[2] == '<>') + { + // really just a fancy way of saying foo <> bar or (foo is NULL XOR bar is NULL) but SQL has no XOR :P + return '(' . $args[0] . ' OR ((' . $args[1] . ' is NULL AND ' . $args[3] . ' is NOT NULL) OR (' . $args[1] . ' is NOT NULL AND ' . $args[3] . ' is NULL)))'; + } + } + else + { + return $this->_rewrite_where($args[0]); + } + } + + /** + * Oracle specific code to handle it's lack of sanity + * @access private + */ + function _rewrite_where($where_clause) + { + preg_match_all('/\s*(AND|OR)?\s*([\w_.()]++)\s*(?:(=|<[=>]?|>=?|LIKE)\s*((?>\'(?>[^\']++|\'\')*+\'|[\d-.()]+))|((NOT )?IN\s*\((?>\'(?>[^\']++|\'\')*+\',? ?|[\d-.]+,? ?)*+\)))/', $where_clause, $result, PREG_SET_ORDER); + $out = ''; + foreach ($result as $val) + { + if (!isset($val[5])) + { + if ($val[4] !== "''") + { + $out .= $val[0]; + } + else + { + $out .= ' ' . $val[1] . ' ' . $val[2]; + if ($val[3] == '=') + { + $out .= ' is NULL'; + } + else if ($val[3] == '<>') + { + $out .= ' is NOT NULL'; + } + } + } + else + { + $in_clause = array(); + $sub_exp = substr($val[5], strpos($val[5], '(') + 1, -1); + $extra = false; + preg_match_all('/\'(?>[^\']++|\'\')*+\'|[\d-.]++/', $sub_exp, $sub_vals, PREG_PATTERN_ORDER); + $i = 0; + foreach ($sub_vals[0] as $sub_val) + { + // two things: + // 1) This determines if an empty string was in the IN clausing, making us turn it into a NULL comparison + // 2) This fixes the 1000 list limit that Oracle has (ORA-01795) + if ($sub_val !== "''") + { + $in_clause[(int) $i++/1000][] = $sub_val; + } + else + { + $extra = true; + } + } + if (!$extra && $i < 1000) + { + $out .= $val[0]; + } + else + { + $out .= ' ' . $val[1] . '('; + $in_array = array(); + + // constuct each IN() clause + foreach ($in_clause as $in_values) + { + $in_array[] = $val[2] . ' ' . (isset($val[6]) ? $val[6] : '') . 'IN(' . implode(', ', $in_values) . ')'; + } + + // Join the IN() clauses against a few ORs (IN is just a nicer OR anyway) + $out .= implode(' OR ', $in_array); + + // handle the empty string case + if ($extra) + { + $out .= ' OR ' . $val[2] . ' is ' . (isset($val[6]) ? $val[6] : '') . 'NULL'; + } + $out .= ')'; + + unset($in_array, $in_clause); + } + } + } + + return $out; + } + + /** + * Base query method + * + * @param string $query Contains the SQL query which shall be executed + * @param int $cache_ttl Either 0 to avoid caching or the time in seconds which the result shall be kept in cache + * @return mixed When casted to bool the returned value returns true on success and false on failure + * + * @access public + */ + function sql_query($query = '', $cache_ttl = 0) + { + if ($query != '') + { + global $cache; + + // EXPLAIN only in extra debug mode + if (defined('DEBUG')) + { + $this->sql_report('start', $query); + } + + $this->last_query_text = $query; + $this->query_result = ($cache && $cache_ttl) ? $cache->sql_load($query) : false; + $this->sql_add_num_queries($this->query_result); + + if ($this->query_result === false) + { + $in_transaction = false; + if (!$this->transaction) + { + $this->sql_transaction('begin'); + } + else + { + $in_transaction = true; + } + + $array = array(); + + // We overcome Oracle's 4000 char limit by binding vars + if (strlen($query) > 4000) + { + if (preg_match('/^(INSERT INTO[^(]++)\\(([^()]+)\\) VALUES[^(]++\\((.*?)\\)$/sU', $query, $regs)) + { + if (strlen($regs[3]) > 4000) + { + $cols = explode(', ', $regs[2]); + + preg_match_all('/\'(?:[^\']++|\'\')*+\'|[\d-.]+/', $regs[3], $vals, PREG_PATTERN_ORDER); + +/* The code inside this comment block breaks clob handling, but does allow the + database restore script to work. If you want to allow no posts longer than 4KB + and/or need the db restore script, uncomment this. + + + if (sizeof($cols) !== sizeof($vals)) + { + // Try to replace some common data we know is from our restore script or from other sources + $regs[3] = str_replace("'||chr(47)||'", '/', $regs[3]); + $_vals = explode(', ', $regs[3]); + + $vals = array(); + $is_in_val = false; + $i = 0; + $string = ''; + + foreach ($_vals as $value) + { + if (strpos($value, "'") === false && !$is_in_val) + { + $vals[$i++] = $value; + continue; + } + + if (substr($value, -1) === "'") + { + $vals[$i] = $string . (($is_in_val) ? ', ' : '') . $value; + $string = ''; + $is_in_val = false; + + if ($vals[$i][0] !== "'") + { + $vals[$i] = "''" . $vals[$i]; + } + $i++; + continue; + } + else + { + $string .= (($is_in_val) ? ', ' : '') . $value; + $is_in_val = true; + } + } + + if ($string) + { + // New value if cols != value + $vals[(sizeof($cols) !== sizeof($vals)) ? $i : $i - 1] .= $string; + } + + $vals = array(0 => $vals); + } +*/ + + $inserts = $vals[0]; + unset($vals); + + foreach ($inserts as $key => $value) + { + if (!empty($value) && $value[0] === "'" && strlen($value) > 4002) // check to see if this thing is greater than the max + 'x2 + { + $inserts[$key] = ':' . strtoupper($cols[$key]); + $array[$inserts[$key]] = str_replace("''", "'", substr($value, 1, -1)); + } + } + + $query = $regs[1] . '(' . $regs[2] . ') VALUES (' . implode(', ', $inserts) . ')'; + } + } + else if (preg_match_all('/^(UPDATE [\\w_]++\\s+SET )([\\w_]++\\s*=\\s*(?:\'(?:[^\']++|\'\')*+\'|[\d-.]+)(?:,\\s*[\\w_]++\\s*=\\s*(?:\'(?:[^\']++|\'\')*+\'|[\d-.]+))*+)\\s+(WHERE.*)$/s', $query, $data, PREG_SET_ORDER)) + { + if (strlen($data[0][2]) > 4000) + { + $update = $data[0][1]; + $where = $data[0][3]; + preg_match_all('/([\\w_]++)\\s*=\\s*(\'(?:[^\']++|\'\')*+\'|[\d-.]++)/', $data[0][2], $temp, PREG_SET_ORDER); + unset($data); + + $cols = array(); + foreach ($temp as $value) + { + if (!empty($value[2]) && $value[2][0] === "'" && strlen($value[2]) > 4002) // check to see if this thing is greater than the max + 'x2 + { + $cols[] = $value[1] . '=:' . strtoupper($value[1]); + $array[$value[1]] = str_replace("''", "'", substr($value[2], 1, -1)); + } + else + { + $cols[] = $value[1] . '=' . $value[2]; + } + } + + $query = $update . implode(', ', $cols) . ' ' . $where; + unset($cols); + } + } + } + + switch (substr($query, 0, 6)) + { + case 'DELETE': + if (preg_match('/^(DELETE FROM [\w_]++ WHERE)((?:\s*(?:AND|OR)?\s*[\w_]+\s*(?:(?:=|<>)\s*(?>\'(?>[^\']++|\'\')*+\'|[\d-.]+)|(?:NOT )?IN\s*\((?>\'(?>[^\']++|\'\')*+\',? ?|[\d-.]+,? ?)*+\)))*+)$/', $query, $regs)) + { + $query = $regs[1] . $this->_rewrite_where($regs[2]); + unset($regs); + } + break; + + case 'UPDATE': + if (preg_match('/^(UPDATE [\\w_]++\\s+SET [\\w_]+\s*=\s*(?:\'(?:[^\']++|\'\')*+\'|[\d-.]++|:\w++)(?:, [\\w_]+\s*=\s*(?:\'(?:[^\']++|\'\')*+\'|[\d-.]++|:\w++))*+\\s+WHERE)(.*)$/s', $query, $regs)) + { + $query = $regs[1] . $this->_rewrite_where($regs[2]); + unset($regs); + } + break; + + case 'SELECT': + $query = preg_replace_callback('/([\w_.]++)\s*(?:(=|<>)\s*(?>\'(?>[^\']++|\'\')*+\'|[\d-.]++|([\w_.]++))|(?:NOT )?IN\s*\((?>\'(?>[^\']++|\'\')*+\',? ?|[\d-.]++,? ?)*+\))/', array($this, '_rewrite_col_compare'), $query); + break; + } + + $this->query_result = @ociparse($this->db_connect_id, $query); + + foreach ($array as $key => $value) + { + @ocibindbyname($this->query_result, $key, $array[$key], -1); + } + + $success = @ociexecute($this->query_result, OCI_DEFAULT); + + if (!$success) + { + $this->sql_error($query); + $this->query_result = false; + } + else + { + if (!$in_transaction) + { + $this->sql_transaction('commit'); + } + } + + if (defined('DEBUG')) + { + $this->sql_report('stop', $query); + } + + if ($cache && $cache_ttl) + { + $this->open_queries[(int) $this->query_result] = $this->query_result; + $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); + } + else if (strpos($query, 'SELECT') === 0 && $this->query_result) + { + $this->open_queries[(int) $this->query_result] = $this->query_result; + } + } + else if (defined('DEBUG')) + { + $this->sql_report('fromcache', $query); + } + } + else + { + return false; + } + + return $this->query_result; + } + + /** + * Build LIMIT query + */ + function _sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) + { + $this->query_result = false; + + $query = 'SELECT * FROM (SELECT /*+ FIRST_ROWS */ rownum AS xrownum, a.* FROM (' . $query . ') a WHERE rownum <= ' . ($offset + $total) . ') WHERE xrownum >= ' . $offset; + + return $this->sql_query($query, $cache_ttl); + } + + /** + * Return number of affected rows + */ + function sql_affectedrows() + { + return ($this->query_result) ? @ocirowcount($this->query_result) : false; + } + + /** + * Fetch current row + */ + function sql_fetchrow($query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($cache && $cache->sql_exists($query_id)) + { + return $cache->sql_fetchrow($query_id); + } + + if ($query_id !== false) + { + $row = array(); + $result = @ocifetchinto($query_id, $row, OCI_ASSOC + OCI_RETURN_NULLS); + + if (!$result || !$row) + { + return false; + } + + $result_row = array(); + foreach ($row as $key => $value) + { + // Oracle treats empty strings as null + if (is_null($value)) + { + $value = ''; + } + + // OCI->CLOB? + if (is_object($value)) + { + $value = $value->load(); + } + + $result_row[strtolower($key)] = $value; + } + + return $result_row; + } + + return false; + } + + /** + * Seek to given row number + * rownum is zero-based + */ + function sql_rowseek($rownum, &$query_id) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($cache && $cache->sql_exists($query_id)) + { + return $cache->sql_rowseek($rownum, $query_id); + } + + if ($query_id === false) + { + return false; + } + + // Reset internal pointer + @ociexecute($query_id, OCI_DEFAULT); + + // We do not fetch the row for rownum == 0 because then the next resultset would be the second row + for ($i = 0; $i < $rownum; $i++) + { + if (!$this->sql_fetchrow($query_id)) + { + return false; + } + } + + return true; + } + + /** + * Get last inserted id after insert statement + */ + function sql_nextid() + { + $query_id = $this->query_result; + + if ($query_id !== false && $this->last_query_text != '') + { + if (preg_match('#^INSERT[\t\n ]+INTO[\t\n ]+([a-z0-9\_\-]+)#is', $this->last_query_text, $tablename)) + { + $query = 'SELECT ' . $tablename[1] . '_seq.currval FROM DUAL'; + $stmt = @ociparse($this->db_connect_id, $query); + @ociexecute($stmt, OCI_DEFAULT); + + $temp_result = @ocifetchinto($stmt, $temp_array, OCI_ASSOC + OCI_RETURN_NULLS); + @ocifreestatement($stmt); + + if ($temp_result) + { + return $temp_array['CURRVAL']; + } + else + { + return false; + } + } + } + + return false; + } + + /** + * Free sql result + */ + function sql_freeresult($query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($cache && $cache->sql_exists($query_id)) + { + return $cache->sql_freeresult($query_id); + } + + if (isset($this->open_queries[(int) $query_id])) + { + unset($this->open_queries[(int) $query_id]); + return @ocifreestatement($query_id); + } + + return false; + } + + /** + * Escape string used in sql query + */ + function sql_escape($msg) + { + return str_replace(array("'", "\0"), array("''", ''), $msg); + } + + /** + * Build LIKE expression + * @access private + */ + function _sql_like_expression($expression) + { + return $expression . " ESCAPE '\\'"; + } + + function _sql_custom_build($stage, $data) + { + return $data; + } + + function _sql_bit_and($column_name, $bit, $compare = '') + { + return 'BITAND(' . $column_name . ', ' . (1 << $bit) . ')' . (($compare) ? ' ' . $compare : ''); + } + + function _sql_bit_or($column_name, $bit, $compare = '') + { + return 'BITOR(' . $column_name . ', ' . (1 << $bit) . ')' . (($compare) ? ' ' . $compare : ''); + } + + /** + * return sql error array + * @access private + */ + function _sql_error() + { + if (function_exists('ocierror')) + { + $error = @ocierror(); + $error = (!$error) ? @ocierror($this->query_result) : $error; + $error = (!$error) ? @ocierror($this->db_connect_id) : $error; + + if ($error) + { + $this->last_error_result = $error; + } + else + { + $error = (isset($this->last_error_result) && $this->last_error_result) ? $this->last_error_result : array(); + } + } + else + { + $error = array( + 'message' => $this->connect_error, + 'code' => '', + ); + } + + return $error; + } + + /** + * Close sql connection + * @access private + */ + function _sql_close() + { + return @ocilogoff($this->db_connect_id); + } + + /** + * Build db-specific report + * @access private + */ + function _sql_report($mode, $query = '') + { + switch ($mode) + { + case 'start': + + $html_table = false; + + // Grab a plan table, any will do + $sql = "SELECT table_name + FROM USER_TABLES + WHERE table_name LIKE '%PLAN_TABLE%'"; + $stmt = ociparse($this->db_connect_id, $sql); + ociexecute($stmt); + $result = array(); + + if (ocifetchinto($stmt, $result, OCI_ASSOC + OCI_RETURN_NULLS)) + { + $table = $result['TABLE_NAME']; + + // This is the statement_id that will allow us to track the plan + $statement_id = substr(md5($query), 0, 30); + + // Remove any stale plans + $stmt2 = ociparse($this->db_connect_id, "DELETE FROM $table WHERE statement_id='$statement_id'"); + ociexecute($stmt2); + ocifreestatement($stmt2); + + // Explain the plan + $sql = "EXPLAIN PLAN + SET STATEMENT_ID = '$statement_id' + FOR $query"; + $stmt2 = ociparse($this->db_connect_id, $sql); + ociexecute($stmt2); + ocifreestatement($stmt2); + + // Get the data from the plan + $sql = "SELECT operation, options, object_name, object_type, cardinality, cost + FROM plan_table + START WITH id = 0 AND statement_id = '$statement_id' + CONNECT BY PRIOR id = parent_id + AND statement_id = '$statement_id'"; + $stmt2 = ociparse($this->db_connect_id, $sql); + ociexecute($stmt2); + + $row = array(); + while (ocifetchinto($stmt2, $row, OCI_ASSOC + OCI_RETURN_NULLS)) + { + $html_table = $this->sql_report('add_select_row', $query, $html_table, $row); + } + + ocifreestatement($stmt2); + + // Remove the plan we just made, we delete them on request anyway + $stmt2 = ociparse($this->db_connect_id, "DELETE FROM $table WHERE statement_id='$statement_id'"); + ociexecute($stmt2); + ocifreestatement($stmt2); + } + + ocifreestatement($stmt); + + if ($html_table) + { + $this->html_hold .= ''; + } + + break; + + case 'fromcache': + $endtime = explode(' ', microtime()); + $endtime = $endtime[0] + $endtime[1]; + + $result = @ociparse($this->db_connect_id, $query); + $success = @ociexecute($result, OCI_DEFAULT); + $row = array(); + + while (@ocifetchinto($result, $row, OCI_ASSOC + OCI_RETURN_NULLS)) + { + // Take the time spent on parsing rows into account + } + @ocifreestatement($result); + + $splittime = explode(' ', microtime()); + $splittime = $splittime[0] + $splittime[1]; + + $this->sql_report('record_fromcache', $query, $endtime, $splittime); + + break; + } + } +} diff --git a/phpBB/phpbb/db/driver/postgres.php b/phpBB/phpbb/db/driver/postgres.php new file mode 100644 index 0000000000..14854d179d --- /dev/null +++ b/phpBB/phpbb/db/driver/postgres.php @@ -0,0 +1,491 @@ +dbname = $database; + if (strpos($database, '.') !== false) + { + list($database, $schema) = explode('.', $database); + } + $connect_string .= "dbname=$database"; + } + + $this->persistency = $persistency; + + if ($this->persistency) + { + if (!function_exists('pg_pconnect')) + { + $this->connect_error = 'pg_pconnect function does not exist, is pgsql extension installed?'; + return $this->sql_error(''); + } + $collector = new phpbb_error_collector; + $collector->install(); + $this->db_connect_id = (!$new_link) ? @pg_pconnect($connect_string) : @pg_pconnect($connect_string, PGSQL_CONNECT_FORCE_NEW); + } + else + { + if (!function_exists('pg_connect')) + { + $this->connect_error = 'pg_connect function does not exist, is pgsql extension installed?'; + return $this->sql_error(''); + } + $collector = new phpbb_error_collector; + $collector->install(); + $this->db_connect_id = (!$new_link) ? @pg_connect($connect_string) : @pg_connect($connect_string, PGSQL_CONNECT_FORCE_NEW); + } + + $collector->uninstall(); + + if ($this->db_connect_id) + { + if (version_compare($this->sql_server_info(true), '8.2', '>=')) + { + $this->multi_insert = true; + } + + if ($schema !== '') + { + @pg_query($this->db_connect_id, 'SET search_path TO ' . $schema); + } + return $this->db_connect_id; + } + + $this->connect_error = $collector->format_errors(); + return $this->sql_error(''); + } + + /** + * Version information about used database + * @param bool $raw if true, only return the fetched sql_server_version + * @param bool $use_cache If true, it is safe to retrieve the value from the cache + * @return string sql server version + */ + function sql_server_info($raw = false, $use_cache = true) + { + global $cache; + + if (!$use_cache || empty($cache) || ($this->sql_server_version = $cache->get('pgsql_version')) === false) + { + $query_id = @pg_query($this->db_connect_id, 'SELECT VERSION() AS version'); + $row = @pg_fetch_assoc($query_id, null); + @pg_free_result($query_id); + + $this->sql_server_version = (!empty($row['version'])) ? trim(substr($row['version'], 10)) : 0; + + if (!empty($cache) && $use_cache) + { + $cache->put('pgsql_version', $this->sql_server_version); + } + } + + return ($raw) ? $this->sql_server_version : 'PostgreSQL ' . $this->sql_server_version; + } + + /** + * SQL Transaction + * @access private + */ + function _sql_transaction($status = 'begin') + { + switch ($status) + { + case 'begin': + return @pg_query($this->db_connect_id, 'BEGIN'); + break; + + case 'commit': + return @pg_query($this->db_connect_id, 'COMMIT'); + break; + + case 'rollback': + return @pg_query($this->db_connect_id, 'ROLLBACK'); + break; + } + + return true; + } + + /** + * Base query method + * + * @param string $query Contains the SQL query which shall be executed + * @param int $cache_ttl Either 0 to avoid caching or the time in seconds which the result shall be kept in cache + * @return mixed When casted to bool the returned value returns true on success and false on failure + * + * @access public + */ + function sql_query($query = '', $cache_ttl = 0) + { + if ($query != '') + { + global $cache; + + // EXPLAIN only in extra debug mode + if (defined('DEBUG')) + { + $this->sql_report('start', $query); + } + + $this->last_query_text = $query; + $this->query_result = ($cache && $cache_ttl) ? $cache->sql_load($query) : false; + $this->sql_add_num_queries($this->query_result); + + if ($this->query_result === false) + { + if (($this->query_result = @pg_query($this->db_connect_id, $query)) === false) + { + $this->sql_error($query); + } + + if (defined('DEBUG')) + { + $this->sql_report('stop', $query); + } + + if ($cache && $cache_ttl) + { + $this->open_queries[(int) $this->query_result] = $this->query_result; + $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); + } + else if (strpos($query, 'SELECT') === 0 && $this->query_result) + { + $this->open_queries[(int) $this->query_result] = $this->query_result; + } + } + else if (defined('DEBUG')) + { + $this->sql_report('fromcache', $query); + } + } + else + { + return false; + } + + return $this->query_result; + } + + /** + * Build db-specific query data + * @access private + */ + function _sql_custom_build($stage, $data) + { + return $data; + } + + /** + * Build LIMIT query + */ + function _sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) + { + $this->query_result = false; + + // if $total is set to 0 we do not want to limit the number of rows + if ($total == 0) + { + $total = 'ALL'; + } + + $query .= "\n LIMIT $total OFFSET $offset"; + + return $this->sql_query($query, $cache_ttl); + } + + /** + * Return number of affected rows + */ + function sql_affectedrows() + { + return ($this->query_result) ? @pg_affected_rows($this->query_result) : false; + } + + /** + * Fetch current row + */ + function sql_fetchrow($query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($cache && $cache->sql_exists($query_id)) + { + return $cache->sql_fetchrow($query_id); + } + + return ($query_id !== false) ? @pg_fetch_assoc($query_id, null) : false; + } + + /** + * Seek to given row number + * rownum is zero-based + */ + function sql_rowseek($rownum, &$query_id) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($cache && $cache->sql_exists($query_id)) + { + return $cache->sql_rowseek($rownum, $query_id); + } + + return ($query_id !== false) ? @pg_result_seek($query_id, $rownum) : false; + } + + /** + * Get last inserted id after insert statement + */ + function sql_nextid() + { + $query_id = $this->query_result; + + if ($query_id !== false && $this->last_query_text != '') + { + if (preg_match("/^INSERT[\t\n ]+INTO[\t\n ]+([a-z0-9\_\-]+)/is", $this->last_query_text, $tablename)) + { + $query = "SELECT currval('" . $tablename[1] . "_seq') AS last_value"; + $temp_q_id = @pg_query($this->db_connect_id, $query); + + if (!$temp_q_id) + { + return false; + } + + $temp_result = @pg_fetch_assoc($temp_q_id, NULL); + @pg_free_result($query_id); + + return ($temp_result) ? $temp_result['last_value'] : false; + } + } + + return false; + } + + /** + * Free sql result + */ + function sql_freeresult($query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($cache && $cache->sql_exists($query_id)) + { + return $cache->sql_freeresult($query_id); + } + + if (isset($this->open_queries[(int) $query_id])) + { + unset($this->open_queries[(int) $query_id]); + return @pg_free_result($query_id); + } + + return false; + } + + /** + * Escape string used in sql query + * Note: Do not use for bytea values if we may use them at a later stage + */ + function sql_escape($msg) + { + return @pg_escape_string($msg); + } + + /** + * Build LIKE expression + * @access private + */ + function _sql_like_expression($expression) + { + return $expression; + } + + /** + * @inheritdoc + */ + function cast_expr_to_bigint($expression) + { + return 'CAST(' . $expression . ' as DECIMAL(255, 0))'; + } + + /** + * @inheritdoc + */ + function cast_expr_to_string($expression) + { + return 'CAST(' . $expression . ' as VARCHAR(255))'; + } + + /** + * return sql error array + * @access private + */ + function _sql_error() + { + // pg_last_error only works when there is an established connection. + // Connection errors have to be tracked by us manually. + if ($this->db_connect_id) + { + $message = @pg_last_error($this->db_connect_id); + } + else + { + $message = $this->connect_error; + } + + return array( + 'message' => $message, + 'code' => '' + ); + } + + /** + * Close sql connection + * @access private + */ + function _sql_close() + { + return @pg_close($this->db_connect_id); + } + + /** + * Build db-specific report + * @access private + */ + function _sql_report($mode, $query = '') + { + switch ($mode) + { + case 'start': + + $explain_query = $query; + if (preg_match('/UPDATE ([a-z0-9_]+).*?WHERE(.*)/s', $query, $m)) + { + $explain_query = 'SELECT * FROM ' . $m[1] . ' WHERE ' . $m[2]; + } + else if (preg_match('/DELETE FROM ([a-z0-9_]+).*?WHERE(.*)/s', $query, $m)) + { + $explain_query = 'SELECT * FROM ' . $m[1] . ' WHERE ' . $m[2]; + } + + if (preg_match('/^SELECT/', $explain_query)) + { + $html_table = false; + + if ($result = @pg_query($this->db_connect_id, "EXPLAIN $explain_query")) + { + while ($row = @pg_fetch_assoc($result, NULL)) + { + $html_table = $this->sql_report('add_select_row', $query, $html_table, $row); + } + } + @pg_free_result($result); + + if ($html_table) + { + $this->html_hold .= ''; + } + } + + break; + + case 'fromcache': + $endtime = explode(' ', microtime()); + $endtime = $endtime[0] + $endtime[1]; + + $result = @pg_query($this->db_connect_id, $query); + while ($void = @pg_fetch_assoc($result, NULL)) + { + // Take the time spent on parsing rows into account + } + @pg_free_result($result); + + $splittime = explode(' ', microtime()); + $splittime = $splittime[0] + $splittime[1]; + + $this->sql_report('record_fromcache', $query, $endtime, $splittime); + + break; + } + } +} diff --git a/phpBB/phpbb/db/driver/sqlite.php b/phpBB/phpbb/db/driver/sqlite.php new file mode 100644 index 0000000000..7188f0daa2 --- /dev/null +++ b/phpBB/phpbb/db/driver/sqlite.php @@ -0,0 +1,365 @@ +persistency = $persistency; + $this->user = $sqluser; + $this->server = $sqlserver . (($port) ? ':' . $port : ''); + $this->dbname = $database; + + $error = ''; + if ($this->persistency) + { + if (!function_exists('sqlite_popen')) + { + $this->connect_error = 'sqlite_popen function does not exist, is sqlite extension installed?'; + return $this->sql_error(''); + } + $this->db_connect_id = @sqlite_popen($this->server, 0666, $error); + } + else + { + if (!function_exists('sqlite_open')) + { + $this->connect_error = 'sqlite_open function does not exist, is sqlite extension installed?'; + return $this->sql_error(''); + } + $this->db_connect_id = @sqlite_open($this->server, 0666, $error); + } + + if ($this->db_connect_id) + { + @sqlite_query('PRAGMA short_column_names = 1', $this->db_connect_id); +// @sqlite_query('PRAGMA encoding = "UTF-8"', $this->db_connect_id); + } + + return ($this->db_connect_id) ? true : array('message' => $error); + } + + /** + * Version information about used database + * @param bool $raw if true, only return the fetched sql_server_version + * @param bool $use_cache if true, it is safe to retrieve the stored value from the cache + * @return string sql server version + */ + function sql_server_info($raw = false, $use_cache = true) + { + global $cache; + + if (!$use_cache || empty($cache) || ($this->sql_server_version = $cache->get('sqlite_version')) === false) + { + $result = @sqlite_query('SELECT sqlite_version() AS version', $this->db_connect_id); + $row = @sqlite_fetch_array($result, SQLITE_ASSOC); + + $this->sql_server_version = (!empty($row['version'])) ? $row['version'] : 0; + + if (!empty($cache) && $use_cache) + { + $cache->put('sqlite_version', $this->sql_server_version); + } + } + + return ($raw) ? $this->sql_server_version : 'SQLite ' . $this->sql_server_version; + } + + /** + * SQL Transaction + * @access private + */ + function _sql_transaction($status = 'begin') + { + switch ($status) + { + case 'begin': + return @sqlite_query('BEGIN', $this->db_connect_id); + break; + + case 'commit': + return @sqlite_query('COMMIT', $this->db_connect_id); + break; + + case 'rollback': + return @sqlite_query('ROLLBACK', $this->db_connect_id); + break; + } + + return true; + } + + /** + * Base query method + * + * @param string $query Contains the SQL query which shall be executed + * @param int $cache_ttl Either 0 to avoid caching or the time in seconds which the result shall be kept in cache + * @return mixed When casted to bool the returned value returns true on success and false on failure + * + * @access public + */ + function sql_query($query = '', $cache_ttl = 0) + { + if ($query != '') + { + global $cache; + + // EXPLAIN only in extra debug mode + if (defined('DEBUG')) + { + $this->sql_report('start', $query); + } + + $this->query_result = ($cache && $cache_ttl) ? $cache->sql_load($query) : false; + $this->sql_add_num_queries($this->query_result); + + if ($this->query_result === false) + { + if (($this->query_result = @sqlite_query($query, $this->db_connect_id)) === false) + { + $this->sql_error($query); + } + + if (defined('DEBUG')) + { + $this->sql_report('stop', $query); + } + + if ($cache && $cache_ttl) + { + $this->open_queries[(int) $this->query_result] = $this->query_result; + $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); + } + else if (strpos($query, 'SELECT') === 0 && $this->query_result) + { + $this->open_queries[(int) $this->query_result] = $this->query_result; + } + } + else if (defined('DEBUG')) + { + $this->sql_report('fromcache', $query); + } + } + else + { + return false; + } + + return $this->query_result; + } + + /** + * Build LIMIT query + */ + function _sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) + { + $this->query_result = false; + + // if $total is set to 0 we do not want to limit the number of rows + if ($total == 0) + { + $total = -1; + } + + $query .= "\n LIMIT " . ((!empty($offset)) ? $offset . ', ' . $total : $total); + + return $this->sql_query($query, $cache_ttl); + } + + /** + * Return number of affected rows + */ + function sql_affectedrows() + { + return ($this->db_connect_id) ? @sqlite_changes($this->db_connect_id) : false; + } + + /** + * Fetch current row + */ + function sql_fetchrow($query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($cache && $cache->sql_exists($query_id)) + { + return $cache->sql_fetchrow($query_id); + } + + return ($query_id !== false) ? @sqlite_fetch_array($query_id, SQLITE_ASSOC) : false; + } + + /** + * Seek to given row number + * rownum is zero-based + */ + function sql_rowseek($rownum, &$query_id) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($cache && $cache->sql_exists($query_id)) + { + return $cache->sql_rowseek($rownum, $query_id); + } + + return ($query_id !== false) ? @sqlite_seek($query_id, $rownum) : false; + } + + /** + * Get last inserted id after insert statement + */ + function sql_nextid() + { + return ($this->db_connect_id) ? @sqlite_last_insert_rowid($this->db_connect_id) : false; + } + + /** + * Free sql result + */ + function sql_freeresult($query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($cache && $cache->sql_exists($query_id)) + { + return $cache->sql_freeresult($query_id); + } + + return true; + } + + /** + * Escape string used in sql query + */ + function sql_escape($msg) + { + return @sqlite_escape_string($msg); + } + + /** + * Correctly adjust LIKE expression for special characters + * For SQLite an underscore is a not-known character... this may change with SQLite3 + */ + function sql_like_expression($expression) + { + // Unlike LIKE, GLOB is case sensitive (unfortunatly). SQLite users need to live with it! + // We only catch * and ? here, not the character map possible on file globbing. + $expression = str_replace(array(chr(0) . '_', chr(0) . '%'), array(chr(0) . '?', chr(0) . '*'), $expression); + + $expression = str_replace(array('?', '*'), array("\?", "\*"), $expression); + $expression = str_replace(array(chr(0) . "\?", chr(0) . "\*"), array('?', '*'), $expression); + + return 'GLOB \'' . $this->sql_escape($expression) . '\''; + } + + /** + * return sql error array + * @access private + */ + function _sql_error() + { + if (function_exists('sqlite_error_string')) + { + $error = array( + 'message' => @sqlite_error_string(@sqlite_last_error($this->db_connect_id)), + 'code' => @sqlite_last_error($this->db_connect_id), + ); + } + else + { + $error = array( + 'message' => $this->connect_error, + 'code' => '', + ); + } + + return $error; + } + + /** + * Build db-specific query data + * @access private + */ + function _sql_custom_build($stage, $data) + { + return $data; + } + + /** + * Close sql connection + * @access private + */ + function _sql_close() + { + return @sqlite_close($this->db_connect_id); + } + + /** + * Build db-specific report + * @access private + */ + function _sql_report($mode, $query = '') + { + switch ($mode) + { + case 'start': + break; + + case 'fromcache': + $endtime = explode(' ', microtime()); + $endtime = $endtime[0] + $endtime[1]; + + $result = @sqlite_query($query, $this->db_connect_id); + while ($void = @sqlite_fetch_array($result, SQLITE_ASSOC)) + { + // Take the time spent on parsing rows into account + } + + $splittime = explode(' ', microtime()); + $splittime = $splittime[0] + $splittime[1]; + + $this->sql_report('record_fromcache', $query, $endtime, $splittime); + + break; + } + } +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_1.php b/phpBB/phpbb/db/migration/data/30x/3_0_1.php new file mode 100644 index 0000000000..c996a0138a --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_1.php @@ -0,0 +1,28 @@ +config['version'], '3.0.1', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_1_rc1'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.1')), + ); + } +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_10.php b/phpBB/phpbb/db/migration/data/30x/3_0_10.php new file mode 100644 index 0000000000..122f93d6b4 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_10.php @@ -0,0 +1,28 @@ +config['version'], '3.0.10', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_10_rc3'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.10')), + ); + } +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_10_rc1.php b/phpBB/phpbb/db/migration/data/30x/3_0_10_rc1.php new file mode 100644 index 0000000000..0ed05812dc --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_10_rc1.php @@ -0,0 +1,30 @@ +config['version'], '3.0.10-rc1', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_9'); + } + + public function update_data() + { + return array( + array('config.add', array('email_max_chunk_size', 50)), + + array('config.update', array('version', '3.0.10-rc1')), + ); + } +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_10_rc2.php b/phpBB/phpbb/db/migration/data/30x/3_0_10_rc2.php new file mode 100644 index 0000000000..b14b3b00aa --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_10_rc2.php @@ -0,0 +1,28 @@ +config['version'], '3.0.10-rc2', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_10_rc1'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.10-rc2')), + ); + } +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_10_rc3.php b/phpBB/phpbb/db/migration/data/30x/3_0_10_rc3.php new file mode 100644 index 0000000000..473057d65d --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_10_rc3.php @@ -0,0 +1,28 @@ +config['version'], '3.0.10-rc3', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_10_rc2'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.10-rc3')), + ); + } +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_11.php b/phpBB/phpbb/db/migration/data/30x/3_0_11.php new file mode 100644 index 0000000000..e063c699cc --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_11.php @@ -0,0 +1,28 @@ +config['version'], '3.0.11', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_11_rc2'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.11')), + ); + } +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_11_rc1.php b/phpBB/phpbb/db/migration/data/30x/3_0_11_rc1.php new file mode 100644 index 0000000000..dddfc0e0e7 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_11_rc1.php @@ -0,0 +1,95 @@ +config['version'], '3.0.11-rc1', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_10'); + } + + public function update_data() + { + return array( + array('custom', array(array(&$this, 'cleanup_deactivated_styles'))), + array('custom', array(array(&$this, 'delete_orphan_private_messages'))), + + array('config.update', array('version', '3.0.11-rc1')), + ); + } + + public function cleanup_deactivated_styles() + { + // Updates users having current style a deactivated one + $sql = 'SELECT style_id + FROM ' . STYLES_TABLE . ' + WHERE style_active = 0'; + $result = $this->sql_query($sql); + + $deactivated_style_ids = array(); + while ($style_id = $this->db->sql_fetchfield('style_id', false, $result)) + { + $deactivated_style_ids[] = (int) $style_id; + } + $this->db->sql_freeresult($result); + + if (!empty($deactivated_style_ids)) + { + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_style = ' . (int) $this->config['default_style'] .' + WHERE ' . $this->db->sql_in_set('user_style', $deactivated_style_ids); + $this->sql_query($sql); + } + } + + public function delete_orphan_private_messages() + { + // Delete orphan private messages + $batch_size = 500; + + $sql_array = array( + 'SELECT' => 'p.msg_id', + 'FROM' => array( + PRIVMSGS_TABLE => 'p', + ), + 'LEFT_JOIN' => array( + array( + 'FROM' => array(PRIVMSGS_TO_TABLE => 't'), + 'ON' => 'p.msg_id = t.msg_id', + ), + ), + 'WHERE' => 't.user_id IS NULL', + ); + $sql = $this->db->sql_build_query('SELECT', $sql_array); + + $result = $this->db->sql_query_limit($sql, $batch_size); + + $delete_pms = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $delete_pms[] = (int) $row['msg_id']; + } + $this->db->sql_freeresult($result); + + if (!empty($delete_pms)) + { + $sql = 'DELETE FROM ' . PRIVMSGS_TABLE . ' + WHERE ' . $this->db->sql_in_set('msg_id', $delete_pms); + $this->sql_query($sql); + + // Return false to have the Migrator call this function again + return false; + } + } +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_11_rc2.php b/phpBB/phpbb/db/migration/data/30x/3_0_11_rc2.php new file mode 100644 index 0000000000..fac8523e8c --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_11_rc2.php @@ -0,0 +1,50 @@ +config['version'], '3.0.11-rc2', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_11_rc1'); + } + + public function update_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'profile_fields' => array( + 'field_show_novalue' => array('BOOL', 0), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_columns' => array( + $this->table_prefix . 'profile_fields' => array( + 'field_show_novalue', + ), + ), + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.11-rc2')), + ); + } +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_12_rc1.php b/phpBB/phpbb/db/migration/data/30x/3_0_12_rc1.php new file mode 100644 index 0000000000..6a31a51201 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_12_rc1.php @@ -0,0 +1,123 @@ +config['version'], '3.0.12-rc1', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_11'); + } + + public function update_data() + { + return array( + array('custom', array(array(&$this, 'update_module_auth'))), + array('custom', array(array(&$this, 'update_bots'))), + array('custom', array(array(&$this, 'disable_bots_from_receiving_pms'))), + + array('config.update', array('version', '3.0.12-rc1')), + ); + } + + public function disable_bots_from_receiving_pms() + { + // Disable receiving pms for bots + $sql = 'SELECT user_id + FROM ' . BOTS_TABLE; + $result = $this->db->sql_query($sql); + + $bot_user_ids = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $bot_user_ids[] = (int) $row['user_id']; + } + $this->db->sql_freeresult($result); + + if (!empty($bot_user_ids)) + { + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_allow_pm = 0 + WHERE ' . $this->db->sql_in_set('user_id', $bot_user_ids); + $this->sql_query($sql); + } + } + + public function update_module_auth() + { + $sql = 'UPDATE ' . MODULES_TABLE . ' + SET module_auth = \'acl_u_sig\' + WHERE module_class = \'ucp\' + AND module_basename = \'profile\' + AND module_mode = \'signature\''; + $this->sql_query($sql); + } + + public function update_bots() + { + // Update bots + if (!function_exists('user_delete')) + { + include($this->phpbb_root_path . 'includes/functions_user.' . $this->php_ext); + } + + $bots_updates = array( + // Bot Deletions + 'NG-Search [Bot]' => false, + 'Nutch/CVS [Bot]' => false, + 'OmniExplorer [Bot]' => false, + 'Seekport [Bot]' => false, + 'Synoo [Bot]' => false, + 'WiseNut [Bot]' => false, + + // Bot Updates + // Bot name to bot user agent map + 'Baidu [Spider]' => 'Baiduspider', + 'Exabot [Bot]' => 'Exabot', + 'Voyager [Bot]' => 'voyager/', + 'W3C [Validator]' => 'W3C_Validator', + ); + + foreach ($bots_updates as $bot_name => $bot_agent) + { + $sql = 'SELECT user_id + FROM ' . USERS_TABLE . ' + WHERE user_type = ' . USER_IGNORE . " + AND username_clean = '" . $this->db->sql_escape(utf8_clean_string($bot_name)) . "'"; + $result = $this->db->sql_query($sql); + $bot_user_id = (int) $this->db->sql_fetchfield('user_id'); + $this->db->sql_freeresult($result); + + if ($bot_user_id) + { + if ($bot_agent === false) + { + $sql = 'DELETE FROM ' . BOTS_TABLE . " + WHERE user_id = $bot_user_id"; + $this->sql_query($sql); + + user_delete('remove', $bot_user_id); + } + else + { + $sql = 'UPDATE ' . BOTS_TABLE . " + SET bot_agent = '" . $this->db->sql_escape($bot_agent) . "' + WHERE user_id = $bot_user_id"; + $this->sql_query($sql); + } + } + } + } +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_1_rc1.php b/phpBB/phpbb/db/migration/data/30x/3_0_1_rc1.php new file mode 100644 index 0000000000..562ccf077c --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_1_rc1.php @@ -0,0 +1,108 @@ +config['version'], '3.0.1-rc1', '>='); + } + + public function update_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'forums' => array( + 'display_subforum_list' => array('BOOL', 1), + ), + $this->table_prefix . 'sessions' => array( + 'session_forum_id' => array('UINT', 0), + ), + ), + 'drop_keys' => array( + $this->table_prefix . 'groups' => array( + 'group_legend', + ), + ), + 'add_index' => array( + $this->table_prefix . 'sessions' => array( + 'session_forum_id' => array('session_forum_id'), + ), + $this->table_prefix . 'groups' => array( + 'group_legend_name' => array('group_legend', 'group_name'), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_columns' => array( + $this->table_prefix . 'forums' => array( + 'display_subforum_list', + ), + $this->table_prefix . 'sessions' => array( + 'session_forum_id', + ), + ), + 'add_index' => array( + $this->table_prefix . 'groups' => array( + 'group_legend' => array('group_legend'), + ), + ), + 'drop_keys' => array( + $this->table_prefix . 'sessions' => array( + 'session_forum_id', + ), + $this->table_prefix . 'groups' => array( + 'group_legend_name', + ), + ), + ); + } + + public function update_data() + { + return array( + array('custom', array(array(&$this, 'fix_unset_last_view_time'))), + array('custom', array(array(&$this, 'reset_smiley_size'))), + + array('config.update', array('version', '3.0.1-rc1')), + ); + } + + public function fix_unset_last_view_time() + { + $sql = 'UPDATE ' . $this->table_prefix . "topics + SET topic_last_view_time = topic_last_post_time + WHERE topic_last_view_time = 0"; + $this->sql_query($sql); + } + + public function reset_smiley_size() + { + // Update smiley sizes + $smileys = array('icon_e_surprised.gif', 'icon_eek.gif', 'icon_cool.gif', 'icon_lol.gif', 'icon_mad.gif', 'icon_razz.gif', 'icon_redface.gif', 'icon_cry.gif', 'icon_evil.gif', 'icon_twisted.gif', 'icon_rolleyes.gif', 'icon_exclaim.gif', 'icon_question.gif', 'icon_idea.gif', 'icon_arrow.gif', 'icon_neutral.gif', 'icon_mrgreen.gif', 'icon_e_ugeek.gif'); + + foreach ($smileys as $smiley) + { + if (file_exists($this->phpbb_root_path . 'images/smilies/' . $smiley)) + { + list($width, $height) = getimagesize($this->phpbb_root_path . 'images/smilies/' . $smiley); + + $sql = 'UPDATE ' . SMILIES_TABLE . ' + SET smiley_width = ' . $width . ', smiley_height = ' . $height . " + WHERE smiley_url = '" . $this->db->sql_escape($smiley) . "'"; + + $this->sql_query($sql); + } + } + } +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_2.php b/phpBB/phpbb/db/migration/data/30x/3_0_2.php new file mode 100644 index 0000000000..eed5acef82 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_2.php @@ -0,0 +1,28 @@ +config['version'], '3.0.2', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_2_rc2'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.2')), + ); + } +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_2_rc1.php b/phpBB/phpbb/db/migration/data/30x/3_0_2_rc1.php new file mode 100644 index 0000000000..a960e90765 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_2_rc1.php @@ -0,0 +1,32 @@ +config['version'], '3.0.2-rc1', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_1'); + } + + public function update_data() + { + return array( + array('config.add', array('referer_validation', '1')), + array('config.add', array('check_attachment_content', '1')), + array('config.add', array('mime_triggers', 'body|head|html|img|plaintext|a href|pre|script|table|title')), + + array('config.update', array('version', '3.0.2-rc1')), + ); + } +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_2_rc2.php b/phpBB/phpbb/db/migration/data/30x/3_0_2_rc2.php new file mode 100644 index 0000000000..8917dfea77 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_2_rc2.php @@ -0,0 +1,80 @@ +config['version'], '3.0.2-rc2', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_2_rc1'); + } + + public function update_schema() + { + return array( + 'change_columns' => array( + $this->table_prefix . 'drafts' => array( + 'draft_subject' => array('STEXT_UNI', ''), + ), + $this->table_prefix . 'forums' => array( + 'forum_last_post_subject' => array('STEXT_UNI', ''), + ), + $this->table_prefix . 'posts' => array( + 'post_subject' => array('STEXT_UNI', '', 'true_sort'), + ), + $this->table_prefix . 'privmsgs' => array( + 'message_subject' => array('STEXT_UNI', ''), + ), + $this->table_prefix . 'topics' => array( + 'topic_title' => array('STEXT_UNI', '', 'true_sort'), + 'topic_last_post_subject' => array('STEXT_UNI', ''), + ), + ), + 'drop_keys' => array( + $this->table_prefix . 'sessions' => array( + 'session_forum_id', + ), + ), + 'add_index' => array( + $this->table_prefix . 'sessions' => array( + 'session_fid' => array('session_forum_id'), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'add_index' => array( + $this->table_prefix . 'sessions' => array( + 'session_forum_id' => array( + 'session_forum_id', + ), + ), + ), + 'drop_keys' => array( + $this->table_prefix . 'sessions' => array( + 'session_fid', + ), + ), + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.2-rc2')), + ); + } +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_3.php b/phpBB/phpbb/db/migration/data/30x/3_0_3.php new file mode 100644 index 0000000000..8984cf7b76 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_3.php @@ -0,0 +1,28 @@ +config['version'], '3.0.3', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_3_rc1'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.3')), + ); + } +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_3_rc1.php b/phpBB/phpbb/db/migration/data/30x/3_0_3_rc1.php new file mode 100644 index 0000000000..4b102e1a2e --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_3_rc1.php @@ -0,0 +1,83 @@ +config['version'], '3.0.3-rc1', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_2'); + } + + public function update_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'styles_template' => array( + 'template_inherits_id' => array('UINT:4', 0), + 'template_inherit_path' => array('VCHAR', ''), + ), + $this->table_prefix . 'groups' => array( + 'group_max_recipients' => array('UINT', 0), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_columns' => array( + $this->table_prefix . 'styles_template' => array( + 'template_inherits_id', + 'template_inherit_path', + ), + $this->table_prefix . 'groups' => array( + 'group_max_recipients', + ), + ), + ); + } + + public function update_data() + { + return array( + array('config.add', array('enable_queue_trigger', '0')), + array('config.add', array('queue_trigger_posts', '3')), + array('config.add', array('pm_max_recipients', '0')), + array('custom', array(array(&$this, 'set_group_default_max_recipients'))), + array('config.add', array('dbms_version', $this->db->sql_server_info(true))), + array('permission.add', array('u_masspm_group', true, 'u_masspm')), + array('custom', array(array(&$this, 'correct_acp_email_permissions'))), + + array('config.update', array('version', '3.0.3-rc1')), + ); + } + + public function correct_acp_email_permissions() + { + $sql = 'UPDATE ' . $this->table_prefix . 'modules + SET module_auth = \'acl_a_email && cfg_email_enable\' + WHERE module_class = \'acp\' + AND module_basename = \'email\''; + $this->sql_query($sql); + } + + public function set_group_default_max_recipients() + { + // Set maximum number of recipients for the registered users, bots, guests group + $sql = 'UPDATE ' . GROUPS_TABLE . ' SET group_max_recipients = 5 + WHERE ' . $this->db->sql_in_set('group_name', array('GUESTS', 'REGISTERED', 'REGISTERED_COPPA', 'BOTS')); + $this->sql_query($sql); + } +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_4.php b/phpBB/phpbb/db/migration/data/30x/3_0_4.php new file mode 100644 index 0000000000..9a0c132e78 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_4.php @@ -0,0 +1,49 @@ +config['version'], '3.0.4', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_4_rc1'); + } + + public function update_data() + { + return array( + array('custom', array(array(&$this, 'rename_log_delete_topic'))), + + array('config.update', array('version', '3.0.4')), + ); + } + + public function rename_log_delete_topic() + { + if ($this->db->sql_layer == 'oracle') + { + // log_operation is CLOB - but we can change this later + $sql = 'UPDATE ' . $this->table_prefix . "log + SET log_operation = 'LOG_DELETE_TOPIC' + WHERE log_operation LIKE 'LOG_TOPIC_DELETED'"; + $this->sql_query($sql); + } + else + { + $sql = 'UPDATE ' . $this->table_prefix . "log + SET log_operation = 'LOG_DELETE_TOPIC' + WHERE log_operation = 'LOG_TOPIC_DELETED'"; + $this->sql_query($sql); + } + } +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_4_rc1.php b/phpBB/phpbb/db/migration/data/30x/3_0_4_rc1.php new file mode 100644 index 0000000000..8ad75a557b --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_4_rc1.php @@ -0,0 +1,123 @@ +config['version'], '3.0.4-rc1', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_3'); + } + + public function update_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'profile_fields' => array( + 'field_show_profile' => array('BOOL', 0), + ), + ), + 'change_columns' => array( + $this->table_prefix . 'styles' => array( + 'style_id' => array('UINT', NULL, 'auto_increment'), + 'template_id' => array('UINT', 0), + 'theme_id' => array('UINT', 0), + 'imageset_id' => array('UINT', 0), + ), + $this->table_prefix . 'styles_imageset' => array( + 'imageset_id' => array('UINT', NULL, 'auto_increment'), + ), + $this->table_prefix . 'styles_imageset_data' => array( + 'image_id' => array('UINT', NULL, 'auto_increment'), + 'imageset_id' => array('UINT', 0), + ), + $this->table_prefix . 'styles_theme' => array( + 'theme_id' => array('UINT', NULL, 'auto_increment'), + ), + $this->table_prefix . 'styles_template' => array( + 'template_id' => array('UINT', NULL, 'auto_increment'), + ), + $this->table_prefix . 'styles_template_data' => array( + 'template_id' => array('UINT', 0), + ), + $this->table_prefix . 'forums' => array( + 'forum_style' => array('UINT', 0), + ), + $this->table_prefix . 'users' => array( + 'user_style' => array('UINT', 0), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_columns' => array( + $this->table_prefix . 'profile_fields' => array( + 'field_show_profile', + ), + ), + ); + } + + public function update_data() + { + return array( + array('custom', array(array(&$this, 'update_custom_profile_fields'))), + + array('config.update', array('version', '3.0.4-rc1')), + ); + } + + public function update_custom_profile_fields() + { + // Update the Custom Profile Fields based on previous settings to the new format + $sql = 'SELECT field_id, field_required, field_show_on_reg, field_hide + FROM ' . PROFILE_FIELDS_TABLE; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $sql_ary = array( + 'field_required' => 0, + 'field_show_on_reg' => 0, + 'field_hide' => 0, + 'field_show_profile'=> 0, + ); + + if ($row['field_required']) + { + $sql_ary['field_required'] = $sql_ary['field_show_on_reg'] = $sql_ary['field_show_profile'] = 1; + } + else if ($row['field_show_on_reg']) + { + $sql_ary['field_show_on_reg'] = $sql_ary['field_show_profile'] = 1; + } + else if ($row['field_hide']) + { + // Only administrators and moderators can see this CPF, if the view is enabled, they can see it, otherwise just admins in the acp_users module + $sql_ary['field_hide'] = 1; + } + else + { + // equivelant to "none", which is the "Display in user control panel" option + $sql_ary['field_show_profile'] = 1; + } + + $this->sql_query('UPDATE ' . $this->table_prefix . 'profile_fields SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . ' WHERE field_id = ' . $row['field_id'], $errored, $error_ary); + } + + $this->db->sql_freeresult($result); + } +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_5.php b/phpBB/phpbb/db/migration/data/30x/3_0_5.php new file mode 100644 index 0000000000..16d2dee457 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_5.php @@ -0,0 +1,28 @@ +config['version'], '3.0.5', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_5_rc1part2'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.5')), + ); + } +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_5_rc1.php b/phpBB/phpbb/db/migration/data/30x/3_0_5_rc1.php new file mode 100644 index 0000000000..ea17cc1e31 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_5_rc1.php @@ -0,0 +1,124 @@ +config['version'], '3.0.5-rc1', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_4'); + } + + public function update_schema() + { + return array( + 'change_columns' => array( + $this->table_prefix . 'forums' => array( + 'forum_style' => array('UINT', 0), + ), + ), + ); + } + + public function update_data() + { + $search_indexing_state = $this->config['search_indexing_state']; + + return array( + array('config.add', array('captcha_gd_wave', 0)), + array('config.add', array('captcha_gd_3d_noise', 1)), + array('config.add', array('captcha_gd_fonts', 1)), + array('config.add', array('confirm_refresh', 1)), + array('config.add', array('max_num_search_keywords', 10)), + array('config.remove', array('search_indexing_state')), + array('config.add', array('search_indexing_state', $search_indexing_state, true)), + array('custom', array(array(&$this, 'hash_old_passwords'))), + array('custom', array(array(&$this, 'update_ichiro_bot'))), + ); + } + + public function hash_old_passwords() + { + $sql = 'SELECT user_id, user_password + FROM ' . $this->table_prefix . 'users + WHERE user_pass_convert = 1'; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + if (strlen($row['user_password']) == 32) + { + $sql_ary = array( + 'user_password' => phpbb_hash($row['user_password']), + ); + + $this->sql_query('UPDATE ' . $this->table_prefix . 'users SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . ' WHERE user_id = ' . $row['user_id']); + } + } + $this->db->sql_freeresult($result); + } + + public function update_ichiro_bot() + { + // Adjust bot entry + $sql = 'UPDATE ' . $this->table_prefix . "bots + SET bot_agent = 'ichiro/' + WHERE bot_agent = 'ichiro/2'"; + $this->sql_query($sql); + } + + public function remove_duplicate_auth_options() + { + // Before we are able to add a unique key to auth_option, we need to remove duplicate entries + $sql = 'SELECT auth_option + FROM ' . $this->table_prefix . 'acl_options + GROUP BY auth_option + HAVING COUNT(*) >= 2'; + $result = $this->db->sql_query($sql); + + $auth_options = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $auth_options[] = $row['auth_option']; + } + $this->db->sql_freeresult($result); + + // Remove specific auth options + if (!empty($auth_options)) + { + foreach ($auth_options as $option) + { + // Select auth_option_ids... the largest id will be preserved + $sql = 'SELECT auth_option_id + FROM ' . ACL_OPTIONS_TABLE . " + WHERE auth_option = '" . $db->sql_escape($option) . "' + ORDER BY auth_option_id DESC"; + // sql_query_limit not possible here, due to bug in postgresql layer + $result = $this->db->sql_query($sql); + + // Skip first row, this is our original auth option we want to preserve + $row = $this->db->sql_fetchrow($result); + + while ($row = $this->db->sql_fetchrow($result)) + { + // Ok, remove this auth option... + $this->sql_query('DELETE FROM ' . ACL_OPTIONS_TABLE . ' WHERE auth_option_id = ' . $row['auth_option_id']); + $this->sql_query('DELETE FROM ' . ACL_ROLES_DATA_TABLE . ' WHERE auth_option_id = ' . $row['auth_option_id']); + $this->sql_query('DELETE FROM ' . ACL_GROUPS_TABLE . ' WHERE auth_option_id = ' . $row['auth_option_id']); + $this->sql_query('DELETE FROM ' . ACL_USERS_TABLE . ' WHERE auth_option_id = ' . $row['auth_option_id']); + } + $this->db->sql_freeresult($result); + } + } + } +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_5_rc1part2.php b/phpBB/phpbb/db/migration/data/30x/3_0_5_rc1part2.php new file mode 100644 index 0000000000..8538347b1a --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_5_rc1part2.php @@ -0,0 +1,42 @@ +config['version'], '3.0.5-rc1', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_5_rc1'); + } + + public function update_schema() + { + return array( + 'drop_keys' => array( + $this->table_prefix . 'acl_options' => array('auth_option'), + ), + 'add_unique_index' => array( + $this->table_prefix . 'acl_options' => array( + 'auth_option' => array('auth_option'), + ), + ), + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.5-rc1')), + ); + } +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_6.php b/phpBB/phpbb/db/migration/data/30x/3_0_6.php new file mode 100644 index 0000000000..bb651dc7cd --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_6.php @@ -0,0 +1,28 @@ +config['version'], '3.0.6', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_6_rc4'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.6')), + ); + } +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_6_rc1.php b/phpBB/phpbb/db/migration/data/30x/3_0_6_rc1.php new file mode 100644 index 0000000000..38c282ebf0 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_6_rc1.php @@ -0,0 +1,324 @@ +config['version'], '3.0.6-rc1', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_5'); + } + + public function update_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'confirm' => array( + 'attempts' => array('UINT', 0), + ), + $this->table_prefix . 'users' => array( + 'user_new' => array('BOOL', 1), + 'user_reminded' => array('TINT:4', 0), + 'user_reminded_time' => array('TIMESTAMP', 0), + ), + $this->table_prefix . 'groups' => array( + 'group_skip_auth' => array('BOOL', 0, 'after' => 'group_founder_manage'), + ), + $this->table_prefix . 'privmsgs' => array( + 'message_reported' => array('BOOL', 0), + ), + $this->table_prefix . 'reports' => array( + 'pm_id' => array('UINT', 0), + ), + $this->table_prefix . 'profile_fields' => array( + 'field_show_on_vt' => array('BOOL', 0), + ), + $this->table_prefix . 'forums' => array( + 'forum_options' => array('UINT:20', 0), + ), + ), + 'change_columns' => array( + $this->table_prefix . 'users' => array( + 'user_options' => array('UINT:11', 230271), + ), + ), + 'add_index' => array( + $this->table_prefix . 'reports' => array( + 'post_id' => array('post_id'), + 'pm_id' => array('pm_id'), + ), + $this->table_prefix . 'posts' => array( + 'post_username' => array('post_username:255'), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_columns' => array( + $this->table_prefix . 'confirm' => array( + 'attempts', + ), + $this->table_prefix . 'users' => array( + 'user_new', + 'user_reminded', + 'user_reminded_time', + ), + $this->table_prefix . 'groups' => array( + 'group_skip_auth', + ), + $this->table_prefix . 'privmsgs' => array( + 'message_reported', + ), + $this->table_prefix . 'reports' => array( + 'pm_id', + ), + $this->table_prefix . 'profile_fields' => array( + 'field_show_on_vt', + ), + $this->table_prefix . 'forums' => array( + 'forum_options', + ), + ), + 'drop_keys' => array( + $this->table_prefix . 'reports' => array( + 'post_id', + 'pm_id', + ), + $this->table_prefix . 'posts' => array( + 'post_username', + ), + ), + ); + } + + public function update_data() + { + return array( + array('config.add', array('captcha_plugin', 'phpbb_captcha_nogd')), + array('if', array( + ($this->config['captcha_gd']), + array('config.update', array('captcha_plugin', 'phpbb_captcha_gd')), + )), + + array('config.add', array('feed_enable', 0)), + array('config.add', array('feed_limit', 10)), + array('config.add', array('feed_overall_forums', 1)), + array('config.add', array('feed_overall_forums_limit', 15)), + array('config.add', array('feed_overall_topics', 0)), + array('config.add', array('feed_overall_topics_limit', 15)), + array('config.add', array('feed_forum', 1)), + array('config.add', array('feed_topic', 1)), + array('config.add', array('feed_item_statistics', 1)), + + array('config.add', array('smilies_per_page', 50)), + array('config.add', array('allow_pm_report', 1)), + array('config.add', array('min_post_chars', 1)), + array('config.add', array('allow_quick_reply', 1)), + array('config.add', array('new_member_post_limit', 0)), + array('config.add', array('new_member_group_default', 0)), + array('config.add', array('delete_time', $this->config['edit_time'])), + + array('config.add', array('allow_avatar', 0)), + array('if', array( + ($this->config['allow_avatar_upload'] || $this->config['allow_avatar_local'] || $this->config['allow_avatar_remote']), + array('config.update', array('allow_avatar', 1)), + )), + array('config.add', array('allow_avatar_remote_upload', 0)), + array('if', array( + ($this->config['allow_avatar_remote'] && $this->config['allow_avatar_upload']), + array('config.update', array('allow_avatar_remote_upload', 1)), + )), + + array('module.add', array( + 'acp', + 'ACP_BOARD_CONFIGURATION', + array( + 'module_basename' => 'acp_board', + 'modes' => array('feed'), + ), + )), + array('module.add', array( + 'acp', + 'ACP_CAT_USERS', + array( + 'module_basename' => 'acp_users', + 'modes' => array('warnings'), + ), + )), + array('module.add', array( + 'acp', + 'ACP_SERVER_CONFIGURATION', + array( + 'module_basename' => 'acp_send_statistics', + 'modes' => array('send_statistics'), + ), + )), + array('module.add', array( + 'acp', + 'ACP_FORUM_BASED_PERMISSIONS', + array( + 'module_basename' => 'acp_permissions', + 'modes' => array('setting_forum_copy'), + ), + )), + array('module.add', array( + 'mcp', + 'MCP_REPORTS', + array( + 'module_basename' => 'mcp_pm_reports', + 'modes' => array('pm_reports','pm_reports_closed','pm_report_details'), + ), + )), + array('custom', array(array(&$this, 'add_newly_registered_group'))), + array('custom', array(array(&$this, 'set_user_options_default'))), + + array('config.update', array('version', '3.0.6-rc1')), + ); + } + + public function set_user_options_default() + { + // 229376 is the added value to enable all three signature options + $sql = 'UPDATE ' . USERS_TABLE . ' SET user_options = user_options + 229376'; + $this->sql_query($sql); + } + + public function add_newly_registered_group() + { + // Add newly_registered group... but check if it already exists (we always supported running the updater on any schema) + $sql = 'SELECT group_id + FROM ' . GROUPS_TABLE . " + WHERE group_name = 'NEWLY_REGISTERED'"; + $result = $this->db->sql_query($sql); + $group_id = (int) $this->db->sql_fetchfield('group_id'); + $this->db->sql_freeresult($result); + + if (!$group_id) + { + $sql = 'INSERT INTO ' . GROUPS_TABLE . " (group_name, group_type, group_founder_manage, group_colour, group_legend, group_avatar, group_desc, group_desc_uid, group_max_recipients) VALUES ('NEWLY_REGISTERED', 3, 0, '', 0, '', '', '', 5)"; + $this->sql_query($sql); + + $group_id = $this->db->sql_nextid(); + } + + // Insert new user role... at the end of the chain + $sql = 'SELECT role_id + FROM ' . ACL_ROLES_TABLE . " + WHERE role_name = 'ROLE_USER_NEW_MEMBER' + AND role_type = 'u_'"; + $result = $this->db->sql_query($sql); + $u_role = (int) $this->db->sql_fetchfield('role_id'); + $this->db->sql_freeresult($result); + + if (!$u_role) + { + $sql = 'SELECT MAX(role_order) as max_order_id + FROM ' . ACL_ROLES_TABLE . " + WHERE role_type = 'u_'"; + $result = $this->db->sql_query($sql); + $next_order_id = (int) $this->db->sql_fetchfield('max_order_id'); + $this->db->sql_freeresult($result); + + $next_order_id++; + + $sql = 'INSERT INTO ' . ACL_ROLES_TABLE . " (role_name, role_description, role_type, role_order) VALUES ('ROLE_USER_NEW_MEMBER', 'ROLE_DESCRIPTION_USER_NEW_MEMBER', 'u_', $next_order_id)"; + $this->sql_query($sql); + $u_role = $this->db->sql_nextid(); + + // Now add the correct data to the roles... + // The standard role says that new users are not able to send a PM, Mass PM, are not able to PM groups + $sql = 'INSERT INTO ' . ACL_ROLES_DATA_TABLE . " (role_id, auth_option_id, auth_setting) SELECT $u_role, auth_option_id, 0 FROM " . ACL_OPTIONS_TABLE . " WHERE auth_option LIKE 'u_%' AND auth_option IN ('u_sendpm', 'u_masspm', 'u_masspm_group')"; + $this->sql_query($sql); + + // Add user role to group + $sql = 'INSERT INTO ' . ACL_GROUPS_TABLE . " (group_id, forum_id, auth_option_id, auth_role_id, auth_setting) VALUES ($group_id, 0, 0, $u_role, 0)"; + $this->sql_query($sql); + } + + // Insert new forum role + $sql = 'SELECT role_id + FROM ' . ACL_ROLES_TABLE . " + WHERE role_name = 'ROLE_FORUM_NEW_MEMBER' + AND role_type = 'f_'"; + $result = $this->db->sql_query($sql); + $f_role = (int) $this->db->sql_fetchfield('role_id'); + $this->db->sql_freeresult($result); + + if (!$f_role) + { + $sql = 'SELECT MAX(role_order) as max_order_id + FROM ' . ACL_ROLES_TABLE . " + WHERE role_type = 'f_'"; + $result = $this->db->sql_query($sql); + $next_order_id = (int) $this->db->sql_fetchfield('max_order_id'); + $this->db->sql_freeresult($result); + + $next_order_id++; + + $sql = 'INSERT INTO ' . ACL_ROLES_TABLE . " (role_name, role_description, role_type, role_order) VALUES ('ROLE_FORUM_NEW_MEMBER', 'ROLE_DESCRIPTION_FORUM_NEW_MEMBER', 'f_', $next_order_id)"; + $this->sql_query($sql); + $f_role = $this->db->sql_nextid(); + + $sql = 'INSERT INTO ' . ACL_ROLES_DATA_TABLE . " (role_id, auth_option_id, auth_setting) SELECT $f_role, auth_option_id, 0 FROM " . ACL_OPTIONS_TABLE . " WHERE auth_option LIKE 'f_%' AND auth_option IN ('f_noapprove')"; + $this->sql_query($sql); + } + + // Set every members user_new column to 0 (old users) only if there is no one yet (this makes sure we do not execute this more than once) + $sql = 'SELECT 1 + FROM ' . USERS_TABLE . ' + WHERE user_new = 0'; + $result = $this->db->sql_query_limit($sql, 1); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if (!$row) + { + $sql = 'UPDATE ' . USERS_TABLE . ' SET user_new = 0'; + $this->sql_query($sql); + } + + // To mimick the old "feature" we will assign the forum role to every forum, regardless of the setting (this makes sure there are no "this does not work!!!! YUO!!!" posts... + // Check if the role is already assigned... + $sql = 'SELECT forum_id + FROM ' . ACL_GROUPS_TABLE . ' + WHERE group_id = ' . $group_id . ' + AND auth_role_id = ' . $f_role; + $result = $this->db->sql_query($sql); + $is_options = (int) $this->db->sql_fetchfield('forum_id'); + $this->db->sql_freeresult($result); + + // Not assigned at all... :/ + if (!$is_options) + { + // Get postable forums + $sql = 'SELECT forum_id + FROM ' . FORUMS_TABLE . ' + WHERE forum_type != ' . FORUM_LINK; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $this->sql_query('INSERT INTO ' . ACL_GROUPS_TABLE . ' (group_id, forum_id, auth_option_id, auth_role_id, auth_setting) VALUES (' . $group_id . ', ' . (int) $row['forum_id'] . ', 0, ' . $f_role . ', 0)'); + } + $this->db->sql_freeresult($result); + } + + // Clear permissions... + include_once($this->phpbb_root_path . 'includes/acp/auth.' . $this->php_ext); + $auth_admin = new auth_admin(); + $auth_admin->acl_clear_prefetch(); + } +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_6_rc2.php b/phpBB/phpbb/db/migration/data/30x/3_0_6_rc2.php new file mode 100644 index 0000000000..a939dbd489 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_6_rc2.php @@ -0,0 +1,28 @@ +config['version'], '3.0.6-rc2', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_6_rc1'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.6-rc2')), + ); + } +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_6_rc3.php b/phpBB/phpbb/db/migration/data/30x/3_0_6_rc3.php new file mode 100644 index 0000000000..b3f09d8ab8 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_6_rc3.php @@ -0,0 +1,40 @@ +config['version'], '3.0.6-rc3', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_6_rc2'); + } + + public function update_data() + { + return array( + array('custom', array(array(&$this, 'update_cp_fields'))), + + array('config.update', array('version', '3.0.6-rc3')), + ); + } + + public function update_cp_fields() + { + // Update the Custom Profile Fields based on previous settings to the new format + $sql = 'UPDATE ' . PROFILE_FIELDS_TABLE . ' + SET field_show_on_vt = 1 + WHERE field_hide = 0 + AND (field_required = 1 OR field_show_on_reg = 1 OR field_show_profile = 1)'; + $this->sql_query($sql); + } +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_6_rc4.php b/phpBB/phpbb/db/migration/data/30x/3_0_6_rc4.php new file mode 100644 index 0000000000..fc2923f99b --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_6_rc4.php @@ -0,0 +1,28 @@ +config['version'], '3.0.6-rc4', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_6_rc3'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.6-rc4')), + ); + } +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_7.php b/phpBB/phpbb/db/migration/data/30x/3_0_7.php new file mode 100644 index 0000000000..9ff2e9e4ab --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_7.php @@ -0,0 +1,28 @@ +config['version'], '3.0.7', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_7_rc2'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.7')), + ); + } +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_7_pl1.php b/phpBB/phpbb/db/migration/data/30x/3_0_7_pl1.php new file mode 100644 index 0000000000..c9cc9d19ac --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_7_pl1.php @@ -0,0 +1,28 @@ +config['version'], '3.0.7-pl1', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_7'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.7-pl1')), + ); + } +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_7_rc1.php b/phpBB/phpbb/db/migration/data/30x/3_0_7_rc1.php new file mode 100644 index 0000000000..ffebf66f2d --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_7_rc1.php @@ -0,0 +1,76 @@ +config['version'], '3.0.7-rc1', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_6'); + } + + public function update_schema() + { + return array( + 'drop_keys' => array( + $this->table_prefix . 'log' => array( + 'log_time', + ), + ), + 'add_index' => array( + $this->table_prefix . 'topics_track' => array( + 'topic_id' => array('topic_id'), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'add_index' => array( + $this->table_prefix . 'log' => array( + 'log_time' => array('log_time'), + ), + ), + 'drop_keys' => array( + $this->table_prefix . 'topics_track' => array( + 'topic_id', + ), + ), + ); + } + + public function update_data() + { + return array( + array('config.add', array('feed_overall', 1)), + array('config.add', array('feed_http_auth', 0)), + array('config.add', array('feed_limit_post', $this->config['feed_limit'])), + array('config.add', array('feed_limit_topic', $this->config['feed_overall_topics_limit'])), + array('config.add', array('feed_topics_new', $this->config['feed_overall_topics'])), + array('config.add', array('feed_topics_active', $this->config['feed_overall_topics'])), + array('custom', array(array(&$this, 'delete_text_templates'))), + + array('config.update', array('version', '3.0.7-rc1')), + ); + } + + public function delete_text_templates() + { + // Delete all text-templates from the template_data + $sql = 'DELETE FROM ' . STYLES_TEMPLATE_DATA_TABLE . ' + WHERE template_filename ' . $this->db->sql_like_expression($this->db->any_char . '.txt'); + $this->sql_query($sql); + } +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_7_rc2.php b/phpBB/phpbb/db/migration/data/30x/3_0_7_rc2.php new file mode 100644 index 0000000000..55bc2bc679 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_7_rc2.php @@ -0,0 +1,73 @@ +config['version'], '3.0.7-rc2', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_7_rc1'); + } + + public function update_data() + { + return array( + array('custom', array(array(&$this, 'update_email_hash'))), + + array('config.update', array('version', '3.0.7-rc2')), + ); + } + + public function update_email_hash($start = 0) + { + $limit = 1000; + + $sql = 'SELECT user_id, user_email, user_email_hash + FROM ' . USERS_TABLE . ' + WHERE user_type <> ' . USER_IGNORE . " + AND user_email <> ''"; + $result = $this->db->sql_query_limit($sql, $limit, $start); + + $i = 0; + while ($row = $this->db->sql_fetchrow($result)) + { + $i++; + + // Snapshot of the phpbb_email_hash() function + // We cannot call it directly because the auto updater updates the DB first. :/ + $user_email_hash = sprintf('%u', crc32(strtolower($row['user_email']))) . strlen($row['user_email']); + + if ($user_email_hash != $row['user_email_hash']) + { + $sql_ary = array( + 'user_email_hash' => $user_email_hash, + ); + + $sql = 'UPDATE ' . USERS_TABLE . ' + SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . ' + WHERE user_id = ' . (int) $row['user_id']; + $this->sql_query($sql); + } + } + $this->db->sql_freeresult($result); + + if ($i < $limit) + { + // Completed + return; + } + + // Return the next start, will be sent to $start when this function is called again + return $start + $limit; + } +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_8.php b/phpBB/phpbb/db/migration/data/30x/3_0_8.php new file mode 100644 index 0000000000..8998ef9627 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_8.php @@ -0,0 +1,28 @@ +config['version'], '3.0.8', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_8_rc1'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.8')), + ); + } +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_8_rc1.php b/phpBB/phpbb/db/migration/data/30x/3_0_8_rc1.php new file mode 100644 index 0000000000..aeff35333e --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_8_rc1.php @@ -0,0 +1,221 @@ +config['version'], '3.0.8-rc1', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_7_pl1'); + } + + public function update_data() + { + return array( + array('custom', array(array(&$this, 'update_file_extension_group_names'))), + array('custom', array(array(&$this, 'update_module_auth'))), + array('custom', array(array(&$this, 'update_bots'))), + array('custom', array(array(&$this, 'delete_orphan_shadow_topics'))), + array('module.add', array( + 'acp', + 'ACP_MESSAGES', + array( + 'module_basename' => 'acp_board', + 'modes' => array('post'), + ), + )), + array('config.add', array('load_unreads_search', 1)), + array('config.update_if_equals', array(600, 'queue_interval', 60)), + array('config.update_if_equals', array(50, 'email_package_size', 20)), + + array('config.update', array('version', '3.0.8-rc1')), + ); + } + + public function update_file_extension_group_names() + { + // Update file extension group names to use language strings. + $sql = 'SELECT lang_dir + FROM ' . LANG_TABLE; + $result = $this->db->sql_query($sql); + + $extension_groups_updated = array(); + while ($lang_dir = $this->db->sql_fetchfield('lang_dir')) + { + $lang_dir = basename($lang_dir); + + // The language strings we need are either in language/.../acp/attachments.php + // in the update package if we're updating to 3.0.8-RC1 or later, + // or they are in language/.../install.php when we're updating from 3.0.7-PL1 or earlier. + // On an already updated board, they can also already be in language/.../acp/attachments.php + // in the board root. + $lang_files = array( + "{$this->phpbb_root_path}install/update/new/language/$lang_dir/acp/attachments.{$this->php_ext}", + "{$this->phpbb_root_path}language/$lang_dir/install.{$this->php_ext}", + "{$this->phpbb_root_path}language/$lang_dir/acp/attachments.{$this->php_ext}", + ); + + foreach ($lang_files as $lang_file) + { + if (!file_exists($lang_file)) + { + continue; + } + + $lang = array(); + include($lang_file); + + foreach($lang as $lang_key => $lang_val) + { + if (isset($extension_groups_updated[$lang_key]) || strpos($lang_key, 'EXT_GROUP_') !== 0) + { + continue; + } + + $sql_ary = array( + 'group_name' => substr($lang_key, 10), // Strip off 'EXT_GROUP_' + ); + + $sql = 'UPDATE ' . EXTENSION_GROUPS_TABLE . ' + SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . " + WHERE group_name = '" . $this->db->sql_escape($lang_val) . "'"; + $this->sql_query($sql); + + $extension_groups_updated[$lang_key] = true; + } + } + } + $this->db->sql_freeresult($result); + } + + public function update_module_auth() + { + $sql = 'UPDATE ' . MODULES_TABLE . ' + SET module_auth = \'cfg_allow_avatar && (cfg_allow_avatar_local || cfg_allow_avatar_remote || cfg_allow_avatar_upload || cfg_allow_avatar_remote_upload)\' + WHERE module_class = \'ucp\' + AND module_basename = \'profile\' + AND module_mode = \'avatar\''; + $this->sql_query($sql); + } + + public function update_bots() + { + $bot_name = 'Bing [Bot]'; + $bot_name_clean = utf8_clean_string($bot_name); + + $sql = 'SELECT user_id + FROM ' . USERS_TABLE . " + WHERE username_clean = '" . $this->db->sql_escape($bot_name_clean) . "'"; + $result = $this->db->sql_query($sql); + $bing_already_added = (bool) $this->db->sql_fetchfield('user_id'); + $this->db->sql_freeresult($result); + + if (!$bing_already_added) + { + $bot_agent = 'bingbot/'; + $bot_ip = ''; + $sql = 'SELECT group_id, group_colour + FROM ' . GROUPS_TABLE . " + WHERE group_name = 'BOTS'"; + $result = $this->db->sql_query($sql); + $group_row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if (!$group_row) + { + // default fallback, should never get here + $group_row['group_id'] = 6; + $group_row['group_colour'] = '9E8DA7'; + } + + if (!function_exists('user_add')) + { + include($this->phpbb_root_path . 'includes/functions_user.' . $this->php_ext); + } + + $user_row = array( + 'user_type' => USER_IGNORE, + 'group_id' => $group_row['group_id'], + 'username' => $bot_name, + 'user_regdate' => time(), + 'user_password' => '', + 'user_colour' => $group_row['group_colour'], + 'user_email' => '', + 'user_lang' => $this->config['default_lang'], + 'user_style' => $this->config['default_style'], + 'user_timezone' => 0, + 'user_dateformat' => $this->config['default_dateformat'], + 'user_allow_massemail' => 0, + ); + + $user_id = user_add($user_row); + + $sql = 'INSERT INTO ' . BOTS_TABLE . ' ' . $this->db->sql_build_array('INSERT', array( + 'bot_active' => 1, + 'bot_name' => (string) $bot_name, + 'user_id' => (int) $user_id, + 'bot_agent' => (string) $bot_agent, + 'bot_ip' => (string) $bot_ip, + )); + + $this->sql_query($sql); + } + } + + public function delete_orphan_shadow_topics() + { + // Delete shadow topics pointing to not existing topics + $batch_size = 500; + + // Set of affected forums we have to resync + $sync_forum_ids = array(); + + $sql_array = array( + 'SELECT' => 't1.topic_id, t1.forum_id', + 'FROM' => array( + TOPICS_TABLE => 't1', + ), + 'LEFT_JOIN' => array( + array( + 'FROM' => array(TOPICS_TABLE => 't2'), + 'ON' => 't1.topic_moved_id = t2.topic_id', + ), + ), + 'WHERE' => 't1.topic_moved_id <> 0 + AND t2.topic_id IS NULL', + ); + $sql = $this->db->sql_build_query('SELECT', $sql_array); + $result = $this->db->sql_query_limit($sql, $batch_size); + + $topic_ids = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $topic_ids[] = (int) $row['topic_id']; + + $sync_forum_ids[(int) $row['forum_id']] = (int) $row['forum_id']; + } + $this->db->sql_freeresult($result); + + if (!empty($topic_ids)) + { + $sql = 'DELETE FROM ' . TOPICS_TABLE . ' + WHERE ' . $this->db->sql_in_set('topic_id', $topic_ids); + $this->db->sql_query($sql); + + // Sync the forums we have deleted shadow topics from. + sync('forum', 'forum_id', $sync_forum_ids, true, true); + + return false; + } + } +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_9.php b/phpBB/phpbb/db/migration/data/30x/3_0_9.php new file mode 100644 index 0000000000..d5269ea6f0 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_9.php @@ -0,0 +1,28 @@ +config['version'], '3.0.9', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_9_rc4'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.9')), + ); + } +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_9_rc1.php b/phpBB/phpbb/db/migration/data/30x/3_0_9_rc1.php new file mode 100644 index 0000000000..4c345b429b --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_9_rc1.php @@ -0,0 +1,124 @@ +config['version'], '3.0.9-rc1', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_8'); + } + + public function update_schema() + { + return array( + 'add_tables' => array( + $this->table_prefix . 'login_attempts' => array( + 'COLUMNS' => array( + // this column was removed from the database updater + // after 3.0.9-RC3 was released. It might still exist + // in 3.0.9-RCX installations and has to be dropped as + // soon as the db_tools class is capable of properly + // removing a primary key. + // 'attempt_id' => array('UINT', NULL, 'auto_increment'), + 'attempt_ip' => array('VCHAR:40', ''), + 'attempt_browser' => array('VCHAR:150', ''), + 'attempt_forwarded_for' => array('VCHAR:255', ''), + 'attempt_time' => array('TIMESTAMP', 0), + 'user_id' => array('UINT', 0), + 'username' => array('VCHAR_UNI:255', 0), + 'username_clean' => array('VCHAR_CI', 0), + ), + //'PRIMARY_KEY' => 'attempt_id', + 'KEYS' => array( + 'att_ip' => array('INDEX', array('attempt_ip', 'attempt_time')), + 'att_for' => array('INDEX', array('attempt_forwarded_for', 'attempt_time')), + 'att_time' => array('INDEX', array('attempt_time')), + 'user_id' => array('INDEX', 'user_id'), + ), + ), + ), + 'change_columns' => array( + $this->table_prefix . 'bbcodes' => array( + 'bbcode_id' => array('USINT', 0), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_tables' => array( + $this->table_prefix . 'login_attempts', + ), + ); + } + + public function update_data() + { + return array( + array('config.add', array('ip_login_limit_max', 50)), + array('config.add', array('ip_login_limit_time', 21600)), + array('config.add', array('ip_login_limit_use_forwarded', 0)), + array('custom', array(array(&$this, 'update_file_extension_group_names'))), + array('custom', array(array(&$this, 'fix_firebird_qa_captcha'))), + + array('config.update', array('version', '3.0.9-rc1')), + ); + } + + public function update_file_extension_group_names() + { + // Update file extension group names to use language strings, again. + $sql = 'SELECT group_id, group_name + FROM ' . EXTENSION_GROUPS_TABLE . ' + WHERE group_name ' . $this->db->sql_like_expression('EXT_GROUP_' . $this->db->any_char); + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $sql_ary = array( + 'group_name' => substr($row['group_name'], 10), // Strip off 'EXT_GROUP_' + ); + + $sql = 'UPDATE ' . EXTENSION_GROUPS_TABLE . ' + SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . ' + WHERE group_id = ' . $row['group_id']; + $this->sql_query($sql); + } + $this->db->sql_freeresult($result); + } + + public function fix_firebird_qa_captcha() + { + // Recover from potentially broken Q&A CAPTCHA table on firebird + // Q&A CAPTCHA was uninstallable, so it's safe to remove these + // without data loss + if ($this->db_tools->sql_layer == 'firebird') + { + $tables = array( + $this->table_prefix . 'captcha_questions', + $this->table_prefix . 'captcha_answers', + $this->table_prefix . 'qa_confirm', + ); + foreach ($tables as $table) + { + if ($this->db_tools->sql_table_exists($table)) + { + $this->db_tools->sql_table_drop($table); + } + } + } + } +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_9_rc2.php b/phpBB/phpbb/db/migration/data/30x/3_0_9_rc2.php new file mode 100644 index 0000000000..c0e662aa45 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_9_rc2.php @@ -0,0 +1,28 @@ +config['version'], '3.0.9-rc2', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_9_rc1'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.9-rc2')), + ); + } +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_9_rc3.php b/phpBB/phpbb/db/migration/data/30x/3_0_9_rc3.php new file mode 100644 index 0000000000..d6d1f14b2e --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_9_rc3.php @@ -0,0 +1,28 @@ +config['version'], '3.0.9-rc3', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_9_rc2'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.9-rc3')), + ); + } +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_9_rc4.php b/phpBB/phpbb/db/migration/data/30x/3_0_9_rc4.php new file mode 100644 index 0000000000..e673249343 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_9_rc4.php @@ -0,0 +1,28 @@ +config['version'], '3.0.9-rc4', '>='); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_9_rc3'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.9-rc4')), + ); + } +} diff --git a/phpBB/phpbb/db/migration/data/30x/local_url_bbcode.php b/phpBB/phpbb/db/migration/data/30x/local_url_bbcode.php new file mode 100644 index 0000000000..f324b8880d --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/local_url_bbcode.php @@ -0,0 +1,57 @@ +db->sql_like_expression($this->db->any_char . 'LOCAL_URL' . $this->db->any_char); + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + if (!class_exists('acp_bbcodes')) + { + global $phpEx; + phpbb_require_updated('includes/acp/acp_bbcodes.' . $phpEx); + } + $bbcode_match = $row['bbcode_match']; + $bbcode_tpl = $row['bbcode_tpl']; + + $acp_bbcodes = new acp_bbcodes(); + $sql_ary = $acp_bbcodes->build_regexp($bbcode_match, $bbcode_tpl); + + $sql = 'UPDATE ' . BBCODES_TABLE . ' + SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . ' + WHERE bbcode_id = ' . (int) $row['bbcode_id']; + $this->sql_query($sql); + } + $this->db->sql_freeresult($result); + } +} diff --git a/phpBB/phpbb/db/migration/data/310/avatars.php b/phpBB/phpbb/db/migration/data/310/avatars.php new file mode 100644 index 0000000000..79547337f7 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/310/avatars.php @@ -0,0 +1,67 @@ +config['allow_avatar_gravatar']); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_11'); + } + + public function update_schema() + { + return array( + 'change_columns' => array( + $this->table_prefix . 'users' => array( + 'user_avatar_type' => array('VCHAR:255', ''), + ), + $this->table_prefix . 'groups' => array( + 'group_avatar_type' => array('VCHAR:255', ''), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'change_columns' => array( + $this->table_prefix . 'users' => array( + 'user_avatar_type' => array('TINT:2', ''), + ), + $this->table_prefix . 'groups' => array( + 'group_avatar_type' => array('TINT:2', ''), + ), + ), + ); + } + + public function update_data() + { + return array( + array('config.add', array('allow_avatar_gravatar', 0)), + array('custom', array(array($this, 'update_module_auth'))), + ); + } + + public function update_module_auth() + { + $sql = 'UPDATE ' . $this->table_prefix . "modules + SET module_auth = 'cfg_allow_avatar' + WHERE module_class = 'ucp' + AND module_basename = 'ucp_profile' + AND module_mode = 'avatar'"; + $this->db->sql_query($sql); + } +} diff --git a/phpBB/phpbb/db/migration/data/310/boardindex.php b/phpBB/phpbb/db/migration/data/310/boardindex.php new file mode 100644 index 0000000000..965e32c15c --- /dev/null +++ b/phpBB/phpbb/db/migration/data/310/boardindex.php @@ -0,0 +1,23 @@ +config['board_index_text']); + } + + public function update_data() + { + return array( + array('config.add', array('board_index_text', '')), + ); + } +} diff --git a/phpBB/phpbb/db/migration/data/310/config_db_text.php b/phpBB/phpbb/db/migration/data/310/config_db_text.php new file mode 100644 index 0000000000..89f211adda --- /dev/null +++ b/phpBB/phpbb/db/migration/data/310/config_db_text.php @@ -0,0 +1,45 @@ +db_tools->sql_table_exists($this->table_prefix . 'config_text'); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_11'); + } + + public function update_schema() + { + return array( + 'add_tables' => array( + $this->table_prefix . 'config_text' => array( + 'COLUMNS' => array( + 'config_name' => array('VCHAR', ''), + 'config_value' => array('MTEXT', ''), + ), + 'PRIMARY_KEY' => 'config_name', + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_tables' => array( + $this->table_prefix . 'config_text', + ), + ); + } +} diff --git a/phpBB/phpbb/db/migration/data/310/dev.php b/phpBB/phpbb/db/migration/data/310/dev.php new file mode 100644 index 0000000000..0fc2950987 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/310/dev.php @@ -0,0 +1,408 @@ +config['version'], '3.1.0-dev', '>='); + } + + static public function depends_on() + { + return array( + 'phpbb_db_migration_data_310_extensions', + 'phpbb_db_migration_data_310_style_update_p2', + 'phpbb_db_migration_data_310_timezone_p2', + 'phpbb_db_migration_data_310_reported_posts_display', + ); + } + + public function update_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'groups' => array( + 'group_teampage' => array('UINT', 0, 'after' => 'group_legend'), + ), + $this->table_prefix . 'profile_fields' => array( + 'field_show_on_pm' => array('BOOL', 0), + ), + $this->table_prefix . 'styles' => array( + 'style_path' => array('VCHAR:100', ''), + 'bbcode_bitfield' => array('VCHAR:255', 'kNg='), + 'style_parent_id' => array('UINT:4', 0), + 'style_parent_tree' => array('TEXT', ''), + ), + $this->table_prefix . 'reports' => array( + 'reported_post_text' => array('MTEXT_UNI', ''), + 'reported_post_uid' => array('VCHAR:8', ''), + 'reported_post_bitfield' => array('VCHAR:255', ''), + ), + ), + 'change_columns' => array( + $this->table_prefix . 'groups' => array( + 'group_legend' => array('UINT', 0), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_columns' => array( + $this->table_prefix . 'groups' => array( + 'group_teampage', + ), + $this->table_prefix . 'profile_fields' => array( + 'field_show_on_pm', + ), + $this->table_prefix . 'styles' => array( + 'style_path', + 'bbcode_bitfield', + 'style_parent_id', + 'style_parent_tree', + ), + $this->table_prefix . 'reports' => array( + 'reported_post_text', + 'reported_post_uid', + 'reported_post_bitfield', + ), + ), + ); + } + + public function update_data() + { + return array( + array('if', array( + (strpos('phpbb_search_', $this->config['search_type']) !== 0), + array('config.update', array('search_type', 'phpbb_search_' . $this->config['search_type'])), + )), + + array('config.add', array('fulltext_postgres_ts_name', 'simple')), + array('config.add', array('fulltext_postgres_min_word_len', 4)), + array('config.add', array('fulltext_postgres_max_word_len', 254)), + array('config.add', array('fulltext_sphinx_stopwords', 0)), + array('config.add', array('fulltext_sphinx_indexer_mem_limit', 512)), + + array('config.add', array('load_jquery_cdn', 0)), + array('config.add', array('load_jquery_url', '//ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js')), + + array('config.add', array('use_system_cron', 0)), + + array('config.add', array('legend_sort_groupname', 0)), + array('config.add', array('teampage_forums', 1)), + array('config.add', array('teampage_memberships', 1)), + + array('config.add', array('load_cpf_pm', 0)), + + array('config.add', array('display_last_subject', 1)), + + array('config.add', array('assets_version', 1)), + + array('config.add', array('site_home_url', '')), + array('config.add', array('site_home_text', '')), + + array('permission.add', array('u_chgprofileinfo', true, 'u_sig')), + + array('module.add', array( + 'acp', + 'ACP_GROUPS', + array( + 'module_basename' => 'acp_groups', + 'modes' => array('position'), + ), + )), + array('module.add', array( + 'acp', + 'ACP_ATTACHMENTS', + array( + 'module_basename' => 'acp_attachments', + 'modes' => array('manage'), + ), + )), + array('module.add', array( + 'acp', + 'ACP_STYLE_MANAGEMENT', + array( + 'module_basename' => 'acp_styles', + 'modes' => array('install', 'cache'), + ), + )), + array('module.add', array( + 'ucp', + 'UCP_PROFILE', + array( + 'module_basename' => 'ucp_profile', + 'modes' => array('autologin_keys'), + ), + )), + // Module will be renamed later + array('module.add', array( + 'acp', + 'ACP_CAT_STYLES', + 'ACP_LANGUAGE' + )), + + array('module.remove', array( + 'acp', + false, + 'ACP_TEMPLATES', + )), + array('module.remove', array( + 'acp', + false, + 'ACP_THEMES', + )), + array('module.remove', array( + 'acp', + false, + 'ACP_IMAGESETS', + )), + + array('custom', array(array($this, 'rename_module_basenames'))), + array('custom', array(array($this, 'rename_styles_module'))), + array('custom', array(array($this, 'add_group_teampage'))), + array('custom', array(array($this, 'update_group_legend'))), + array('custom', array(array($this, 'localise_global_announcements'))), + array('custom', array(array($this, 'update_ucp_pm_basename'))), + array('custom', array(array($this, 'update_ucp_profile_auth'))), + array('custom', array(array($this, 'move_customise_modules'))), + + array('config.update', array('version', '3.1.0-dev')), + ); + } + + public function move_customise_modules() + { + // Move language management to new location in the Customise tab + // First get language module id + $sql = 'SELECT module_id FROM ' . MODULES_TABLE . " + WHERE module_basename = 'acp_language'"; + $result = $this->db->sql_query($sql); + $language_module_id = $this->db->sql_fetchfield('module_id'); + $this->db->sql_freeresult($result); + // Next get language management module id of the one just created + $sql = 'SELECT module_id FROM ' . MODULES_TABLE . " + WHERE module_langname = 'ACP_LANGUAGE'"; + $result = $this->db->sql_query($sql); + $language_management_module_id = $this->db->sql_fetchfield('module_id'); + $this->db->sql_freeresult($result); + + if (!class_exists('acp_modules')) + { + include($this->phpbb_root_path . 'includes/acp/acp_modules.' . $this->php_ext); + } + // acp_modules calls adm_back_link, which is undefined at this point + if (!function_exists('adm_back_link')) + { + include($this->phpbb_root_path . 'includes/functions_acp.' . $this->php_ext); + } + $module_manager = new acp_modules(); + $module_manager->module_class = 'acp'; + $module_manager->move_module($language_module_id, $language_management_module_id); + } + + public function update_ucp_pm_basename() + { + $sql = 'SELECT module_id, module_basename + FROM ' . MODULES_TABLE . " + WHERE module_basename <> 'ucp_pm' AND + module_langname='UCP_PM'"; + $result = $this->db->sql_query_limit($sql, 1); + + if ($row = $this->db->sql_fetchrow($result)) + { + // This update is still not applied. Applying it + + $sql = 'UPDATE ' . MODULES_TABLE . " + SET module_basename = 'ucp_pm' + WHERE module_id = " . (int) $row['module_id']; + + $this->sql_query($sql); + } + $this->db->sql_freeresult($result); + } + + public function update_ucp_profile_auth() + { + // Update the auth setting for the module + $sql = 'UPDATE ' . MODULES_TABLE . " + SET module_auth = 'acl_u_chgprofileinfo' + WHERE module_class = 'ucp' + AND module_basename = 'ucp_profile' + AND module_mode = 'profile_info'"; + $this->sql_query($sql); + } + + public function rename_styles_module() + { + // Rename styles module to Customise + $sql = 'UPDATE ' . MODULES_TABLE . " + SET module_langname = 'ACP_CAT_CUSTOMISE' + WHERE module_langname = 'ACP_CAT_STYLES'"; + $this->sql_query($sql); + } + + public function rename_module_basenames() + { + // rename all module basenames to full classname + $sql = 'SELECT module_id, module_basename, module_class + FROM ' . MODULES_TABLE; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $module_id = (int) $row['module_id']; + unset($row['module_id']); + + if (!empty($row['module_basename']) && !empty($row['module_class'])) + { + // all the class names start with class name or with phpbb_ for auto loading + if (strpos($row['module_basename'], $row['module_class'] . '_') !== 0 && + strpos($row['module_basename'], 'phpbb_') !== 0) + { + $row['module_basename'] = $row['module_class'] . '_' . $row['module_basename']; + + $sql_update = $this->db->sql_build_array('UPDATE', $row); + + $sql = 'UPDATE ' . MODULES_TABLE . ' + SET ' . $sql_update . ' + WHERE module_id = ' . $module_id; + $this->sql_query($sql); + } + } + } + + $this->db->sql_freeresult($result); + } + + public function add_group_teampage() + { + $sql = 'UPDATE ' . GROUPS_TABLE . ' + SET group_teampage = 1 + WHERE group_type = ' . GROUP_SPECIAL . " + AND group_name = 'ADMINISTRATORS'"; + $this->sql_query($sql); + + $sql = 'UPDATE ' . GROUPS_TABLE . ' + SET group_teampage = 2 + WHERE group_type = ' . GROUP_SPECIAL . " + AND group_name = 'GLOBAL_MODERATORS'"; + $this->sql_query($sql); + } + + public function update_group_legend() + { + $sql = 'SELECT group_id + FROM ' . GROUPS_TABLE . ' + WHERE group_legend = 1 + ORDER BY group_name ASC'; + $result = $this->db->sql_query($sql); + + $next_legend = 1; + while ($row = $this->db->sql_fetchrow($result)) + { + $sql = 'UPDATE ' . GROUPS_TABLE . ' + SET group_legend = ' . $next_legend . ' + WHERE group_id = ' . (int) $row['group_id']; + $this->sql_query($sql); + + $next_legend++; + } + $this->db->sql_freeresult($result); + } + + public function localise_global_announcements() + { + // Localise Global Announcements + $sql = 'SELECT topic_id, topic_approved, (topic_replies + 1) AS topic_posts, topic_last_post_id, topic_last_post_subject, topic_last_post_time, topic_last_poster_id, topic_last_poster_name, topic_last_poster_colour + FROM ' . TOPICS_TABLE . ' + WHERE forum_id = 0 + AND topic_type = ' . POST_GLOBAL; + $result = $this->db->sql_query($sql); + + $global_announcements = $update_lastpost_data = array(); + $update_lastpost_data['forum_last_post_time'] = 0; + $update_forum_data = array( + 'forum_posts' => 0, + 'forum_topics' => 0, + 'forum_topics_real' => 0, + ); + + while ($row = $this->db->sql_fetchrow($result)) + { + $global_announcements[] = (int) $row['topic_id']; + + $update_forum_data['forum_posts'] += (int) $row['topic_posts']; + $update_forum_data['forum_topics_real']++; + if ($row['topic_approved']) + { + $update_forum_data['forum_topics']++; + } + + if ($update_lastpost_data['forum_last_post_time'] < $row['topic_last_post_time']) + { + $update_lastpost_data = array( + 'forum_last_post_id' => (int) $row['topic_last_post_id'], + 'forum_last_post_subject' => $row['topic_last_post_subject'], + 'forum_last_post_time' => (int) $row['topic_last_post_time'], + 'forum_last_poster_id' => (int) $row['topic_last_poster_id'], + 'forum_last_poster_name' => $row['topic_last_poster_name'], + 'forum_last_poster_colour' => $row['topic_last_poster_colour'], + ); + } + } + $this->db->sql_freeresult($result); + + if (!empty($global_announcements)) + { + // Update the post/topic-count for the forum and the last-post if needed + $sql = 'SELECT forum_id + FROM ' . FORUMS_TABLE . ' + WHERE forum_type = ' . FORUM_POST; + $result = $this->db->sql_query_limit($sql, 1); + $ga_forum_id = $this->db->sql_fetchfield('forum_id'); + $this->db->sql_freeresult($result); + + $sql = 'SELECT forum_last_post_time + FROM ' . FORUMS_TABLE . ' + WHERE forum_id = ' . $ga_forum_id; + $result = $this->db->sql_query($sql); + $lastpost = (int) $this->db->sql_fetchfield('forum_last_post_time'); + $this->db->sql_freeresult($result); + + $sql_update = 'forum_posts = forum_posts + ' . $update_forum_data['forum_posts'] . ', '; + $sql_update .= 'forum_topics_real = forum_topics_real + ' . $update_forum_data['forum_topics_real'] . ', '; + $sql_update .= 'forum_topics = forum_topics + ' . $update_forum_data['forum_topics']; + if ($lastpost < $update_lastpost_data['forum_last_post_time']) + { + $sql_update .= ', ' . $this->db->sql_build_array('UPDATE', $update_lastpost_data); + } + + $sql = 'UPDATE ' . FORUMS_TABLE . ' + SET ' . $sql_update . ' + WHERE forum_id = ' . $ga_forum_id; + $this->sql_query($sql); + + // Update some forum_ids + $table_ary = array(TOPICS_TABLE, POSTS_TABLE, LOG_TABLE, DRAFTS_TABLE, TOPICS_TRACK_TABLE); + foreach ($table_ary as $table) + { + $sql = "UPDATE $table + SET forum_id = $ga_forum_id + WHERE " . $this->db->sql_in_set('topic_id', $global_announcements); + $this->sql_query($sql); + } + unset($table_ary); + } + } +} diff --git a/phpBB/phpbb/db/migration/data/310/extensions.php b/phpBB/phpbb/db/migration/data/310/extensions.php new file mode 100644 index 0000000000..6a9caa1cfc --- /dev/null +++ b/phpBB/phpbb/db/migration/data/310/extensions.php @@ -0,0 +1,69 @@ +db_tools->sql_table_exists($this->table_prefix . 'ext'); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_11'); + } + + public function update_schema() + { + return array( + 'add_tables' => array( + $this->table_prefix . 'ext' => array( + 'COLUMNS' => array( + 'ext_name' => array('VCHAR', ''), + 'ext_active' => array('BOOL', 0), + 'ext_state' => array('TEXT', ''), + ), + 'KEYS' => array( + 'ext_name' => array('UNIQUE', 'ext_name'), + ), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_tables' => array( + $this->table_prefix . 'ext', + ), + ); + } + + public function update_data() + { + return array( + // Module will be renamed later + array('module.add', array( + 'acp', + 'ACP_CAT_STYLES', + 'ACP_EXTENSION_MANAGEMENT' + )), + array('module.add', array( + 'acp', + 'ACP_EXTENSION_MANAGEMENT', + array( + 'module_basename' => 'acp_extensions', + 'modes' => array('main'), + ), + )), + array('permission.add', array('a_extensions', true, 'a_styles')), + ); + } +} diff --git a/phpBB/phpbb/db/migration/data/310/forgot_password.php b/phpBB/phpbb/db/migration/data/310/forgot_password.php new file mode 100644 index 0000000000..a553e51f35 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/310/forgot_password.php @@ -0,0 +1,28 @@ +config['allow_password_reset']); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_11'); + } + + public function update_data() + { + return array( + array('config.add', array('allow_password_reset', 1)), + ); + } +} diff --git a/phpBB/phpbb/db/migration/data/310/jquery_update.php b/phpBB/phpbb/db/migration/data/310/jquery_update.php new file mode 100644 index 0000000000..dc49f74fcb --- /dev/null +++ b/phpBB/phpbb/db/migration/data/310/jquery_update.php @@ -0,0 +1,31 @@ +config['load_jquery_url'] !== '//ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js'; + } + + static public function depends_on() + { + return array( + 'phpbb_db_migration_data_310_dev', + ); + } + + public function update_data() + { + return array( + array('config.update', array('load_jquery_url', '//ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js')), + ); + } + +} diff --git a/phpBB/phpbb/db/migration/data/310/notification_options_reconvert.php b/phpBB/phpbb/db/migration/data/310/notification_options_reconvert.php new file mode 100644 index 0000000000..d994d7ec5f --- /dev/null +++ b/phpBB/phpbb/db/migration/data/310/notification_options_reconvert.php @@ -0,0 +1,118 @@ +table_prefix . 'user_notifications'; + $insert_buffer = new phpbb_db_sql_insert_buffer($this->db, $insert_table); + + $this->perform_conversion($insert_buffer, $insert_table); + } + + /** + * Perform the conversion (separate for testability) + * + * @param phpbb_db_sql_insert_buffer $insert_buffer + * @param string $insert_table + */ + public function perform_conversion(phpbb_db_sql_insert_buffer $insert_buffer, $insert_table) + { + $sql = 'DELETE FROM ' . $insert_table; + $this->db->sql_query($sql); + + $sql = 'SELECT user_id, user_notify_type, user_notify_pm + FROM ' . USERS_TABLE; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $notification_methods = array(); + + // In-board notification + $notification_methods[] = ''; + + if ($row['user_notify_type'] == NOTIFY_EMAIL || $row['user_notify_type'] == NOTIFY_BOTH) + { + $notification_methods[] = 'email'; + } + + if ($row['user_notify_type'] == NOTIFY_IM || $row['user_notify_type'] == NOTIFY_BOTH) + { + $notification_methods[] = 'jabber'; + } + + // Notifications for posts + foreach (array('post', 'topic') as $item_type) + { + $this->add_method_rows( + $insert_buffer, + $item_type, + 0, + $row['user_id'], + $notification_methods + ); + } + + if ($row['user_notify_pm']) + { + // Notifications for private messages + // User either gets all methods or no method + $this->add_method_rows( + $insert_buffer, + 'pm', + 0, + $row['user_id'], + $notification_methods + ); + } + } + $this->db->sql_freeresult($result); + + $insert_buffer->flush(); + } + + /** + * Insert method rows to DB + * + * @param phpbb_db_sql_insert_buffer $insert_buffer + * @param string $item_type + * @param int $item_id + * @param int $user_id + * @param string $methods + */ + protected function add_method_rows(phpbb_db_sql_insert_buffer $insert_buffer, $item_type, $item_id, $user_id, array $methods) + { + $row_base = array( + 'item_type' => $item_type, + 'item_id' => (int) $item_id, + 'user_id' => (int) $user_id, + 'notify' => 1 + ); + + foreach ($methods as $method) + { + $row_base['method'] = $method; + $insert_buffer->insert($row_base); + } + } +} diff --git a/phpBB/phpbb/db/migration/data/310/notifications.php b/phpBB/phpbb/db/migration/data/310/notifications.php new file mode 100644 index 0000000000..17c939d95a --- /dev/null +++ b/phpBB/phpbb/db/migration/data/310/notifications.php @@ -0,0 +1,96 @@ +db_tools->sql_table_exists($this->table_prefix . 'notifications'); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_310_dev'); + } + + public function update_schema() + { + return array( + 'add_tables' => array( + $this->table_prefix . 'notification_types' => array( + 'COLUMNS' => array( + 'notification_type' => array('VCHAR:255', ''), + 'notification_type_enabled' => array('BOOL', 1), + ), + 'PRIMARY_KEY' => array('notification_type', 'notification_type_enabled'), + ), + $this->table_prefix . 'notifications' => array( + 'COLUMNS' => array( + 'notification_id' => array('UINT', NULL, 'auto_increment'), + 'item_type' => array('VCHAR:255', ''), + 'item_id' => array('UINT', 0), + 'item_parent_id' => array('UINT', 0), + 'user_id' => array('UINT', 0), + 'notification_read' => array('BOOL', 0), + 'notification_time' => array('TIMESTAMP', 1), + 'notification_data' => array('TEXT_UNI', ''), + ), + 'PRIMARY_KEY' => 'notification_id', + 'KEYS' => array( + 'item_ident' => array('INDEX', array('item_type', 'item_id')), + 'user' => array('INDEX', array('user_id', 'notification_read')), + ), + ), + $this->table_prefix . 'user_notifications' => array( + 'COLUMNS' => array( + 'item_type' => array('VCHAR:255', ''), + 'item_id' => array('UINT', 0), + 'user_id' => array('UINT', 0), + 'method' => array('VCHAR:255', ''), + 'notify' => array('BOOL', 1), + ), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_tables' => array( + $this->table_prefix . 'notification_types', + $this->table_prefix . 'notifications', + $this->table_prefix . 'user_notifications', + ), + ); + } + + public function update_data() + { + return array( + array('module.add', array( + 'ucp', + 'UCP_MAIN', + array( + 'module_basename' => 'ucp_notifications', + 'modes' => array('notification_list'), + ), + )), + array('module.add', array( + 'ucp', + 'UCP_PREFS', + array( + 'module_basename' => 'ucp_notifications', + 'modes' => array('notification_options'), + ), + )), + array('config.add', array('load_notifications', 1)), + ); + } +} diff --git a/phpBB/phpbb/db/migration/data/310/notifications_schema_fix.php b/phpBB/phpbb/db/migration/data/310/notifications_schema_fix.php new file mode 100644 index 0000000000..27e63e10d0 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/310/notifications_schema_fix.php @@ -0,0 +1,92 @@ + array( + $this->table_prefix . 'notification_types', + $this->table_prefix . 'notifications', + ), + 'add_tables' => array( + $this->table_prefix . 'notification_types' => array( + 'COLUMNS' => array( + 'notification_type_id' => array('USINT', NULL, 'auto_increment'), + 'notification_type_name' => array('VCHAR:255', ''), + 'notification_type_enabled' => array('BOOL', 1), + ), + 'PRIMARY_KEY' => array('notification_type_id'), + 'KEYS' => array( + 'type' => array('UNIQUE', array('notification_type_name')), + ), + ), + $this->table_prefix . 'notifications' => array( + 'COLUMNS' => array( + 'notification_id' => array('UINT:10', NULL, 'auto_increment'), + 'notification_type_id' => array('USINT', 0), + 'item_id' => array('UINT', 0), + 'item_parent_id' => array('UINT', 0), + 'user_id' => array('UINT', 0), + 'notification_read' => array('BOOL', 0), + 'notification_time' => array('TIMESTAMP', 1), + 'notification_data' => array('TEXT_UNI', ''), + ), + 'PRIMARY_KEY' => 'notification_id', + 'KEYS' => array( + 'item_ident' => array('INDEX', array('notification_type_id', 'item_id')), + 'user' => array('INDEX', array('user_id', 'notification_read')), + ), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_tables' => array( + $this->table_prefix . 'notification_types', + $this->table_prefix . 'notifications', + ), + 'add_tables' => array( + $this->table_prefix . 'notification_types' => array( + 'COLUMNS' => array( + 'notification_type' => array('VCHAR:255', ''), + 'notification_type_enabled' => array('BOOL', 1), + ), + 'PRIMARY_KEY' => array('notification_type', 'notification_type_enabled'), + ), + $this->table_prefix . 'notifications' => array( + 'COLUMNS' => array( + 'notification_id' => array('UINT', NULL, 'auto_increment'), + 'item_type' => array('VCHAR:255', ''), + 'item_id' => array('UINT', 0), + 'item_parent_id' => array('UINT', 0), + 'user_id' => array('UINT', 0), + 'notification_read' => array('BOOL', 0), + 'notification_time' => array('TIMESTAMP', 1), + 'notification_data' => array('TEXT_UNI', ''), + ), + 'PRIMARY_KEY' => 'notification_id', + 'KEYS' => array( + 'item_ident' => array('INDEX', array('item_type', 'item_id')), + 'user' => array('INDEX', array('user_id', 'notification_read')), + ), + ), + ), + ); + } +} diff --git a/phpBB/phpbb/db/migration/data/310/reported_posts_display.php b/phpBB/phpbb/db/migration/data/310/reported_posts_display.php new file mode 100644 index 0000000000..80a0a0e43f --- /dev/null +++ b/phpBB/phpbb/db/migration/data/310/reported_posts_display.php @@ -0,0 +1,47 @@ +db_tools->sql_column_exists($this->table_prefix . 'reports', 'reported_post_enable_bbcode'); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_11'); + } + + public function update_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'reports' => array( + 'reported_post_enable_bbcode' => array('BOOL', 1), + 'reported_post_enable_smilies' => array('BOOL', 1), + 'reported_post_enable_magic_url' => array('BOOL', 1), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_columns' => array( + $this->table_prefix . 'reports' => array( + 'reported_post_enable_bbcode', + 'reported_post_enable_smilies', + 'reported_post_enable_magic_url', + ), + ), + ); + } +} diff --git a/phpBB/phpbb/db/migration/data/310/signature_module_auth.php b/phpBB/phpbb/db/migration/data/310/signature_module_auth.php new file mode 100644 index 0000000000..e4fbb27bcb --- /dev/null +++ b/phpBB/phpbb/db/migration/data/310/signature_module_auth.php @@ -0,0 +1,51 @@ +db->sql_query($sql); + $module_auth = $this->db_sql_fetchfield('module_auth'); + $this->db->sql_freeresult($result); + + return $module_auth === 'acl_u_sig' || $module_auth === false; + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_31x_dev'); + } + + public function update_data() + { + return array( + array('custom', array( + array($this, 'update_signature_module_auth'), + ), + ), + ); + } + + public function update_signature_module_auth() + { + $sql = 'UPDATE ' . MODULES_TABLE . " + SET module_auth = 'acl_u_sig' + WHERE module_class = 'ucp' + AND module_basename = 'ucp_profile' + AND module_mode = 'signature' + AND module_auth = ''"; + $this->db->sql_query($sql); + } +} diff --git a/phpBB/phpbb/db/migration/data/310/softdelete_p1.php b/phpBB/phpbb/db/migration/data/310/softdelete_p1.php new file mode 100644 index 0000000000..84f8eebd4a --- /dev/null +++ b/phpBB/phpbb/db/migration/data/310/softdelete_p1.php @@ -0,0 +1,171 @@ +db_tools->sql_column_exists($this->table_prefix . 'posts', 'post_visibility'); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_310_dev'); + } + + public function update_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'forums' => array( + 'forum_posts_approved' => array('UINT', 0), + 'forum_posts_unapproved' => array('UINT', 0), + 'forum_posts_softdeleted' => array('UINT', 0), + 'forum_topics_approved' => array('UINT', 0), + 'forum_topics_unapproved' => array('UINT', 0), + 'forum_topics_softdeleted' => array('UINT', 0), + ), + $this->table_prefix . 'posts' => array( + 'post_visibility' => array('TINT:3', 0), + 'post_delete_time' => array('TIMESTAMP', 0), + 'post_delete_reason' => array('STEXT_UNI', ''), + 'post_delete_user' => array('UINT', 0), + ), + $this->table_prefix . 'topics' => array( + 'topic_visibility' => array('TINT:3', 0), + 'topic_delete_time' => array('TIMESTAMP', 0), + 'topic_delete_reason' => array('STEXT_UNI', ''), + 'topic_delete_user' => array('UINT', 0), + 'topic_posts_approved' => array('UINT', 0), + 'topic_posts_unapproved' => array('UINT', 0), + 'topic_posts_softdeleted' => array('UINT', 0), + ), + ), + 'add_index' => array( + $this->table_prefix . 'posts' => array( + 'post_visibility' => array('post_visibility'), + ), + $this->table_prefix . 'topics' => array( + 'topic_visibility' => array('topic_visibility'), + 'forum_vis_last' => array('forum_id', 'topic_visibility', 'topic_last_post_id'), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_columns' => array( + $this->table_prefix . 'forums' => array( + 'forum_posts_approved', + 'forum_posts_unapproved', + 'forum_posts_softdeleted', + 'forum_topics_approved', + 'forum_topics_unapproved', + 'forum_topics_softdeleted', + ), + $this->table_prefix . 'posts' => array( + 'post_visibility', + 'post_delete_time', + 'post_delete_reason', + 'post_delete_user', + ), + $this->table_prefix . 'topics' => array( + 'topic_visibility', + 'topic_delete_time', + 'topic_delete_reason', + 'topic_delete_user', + 'topic_posts_approved', + 'topic_posts_unapproved', + 'topic_posts_softdeleted', + ), + ), + 'drop_keys' => array( + $this->table_prefix . 'posts' => array('post_visibility'), + $this->table_prefix . 'topics' => array('topic_visibility', 'forum_vis_last'), + ), + ); + } + + public function update_data() + { + return array( + array('custom', array(array($this, 'update_post_visibility'))), + array('custom', array(array($this, 'update_topic_visibility'))), + array('custom', array(array($this, 'update_topic_forum_counts'))), + + array('permission.add', array('f_softdelete', false)), + array('permission.add', array('m_softdelete', false)), + ); + } + + public function update_post_visibility() + { + $sql = 'UPDATE ' . $this->table_prefix . 'posts + SET post_visibility = post_approved'; + $this->sql_query($sql); + } + + public function update_topic_visibility() + { + $sql = 'UPDATE ' . $this->table_prefix . 'topics + SET topic_visibility = topic_approved'; + $this->sql_query($sql); + } + + public function update_topic_forum_counts() + { + $sql = 'UPDATE ' . $this->table_prefix . 'topics + SET topic_posts_approved = topic_replies + 1, + topic_posts_unapproved = topic_replies_real - topic_replies + WHERE topic_visibility = ' . ITEM_APPROVED; + $this->sql_query($sql); + + $sql = 'UPDATE ' . $this->table_prefix . 'topics + SET topic_posts_approved = 0, + topic_posts_unapproved = (topic_replies_real - topic_replies) + 1 + WHERE topic_visibility = ' . ITEM_UNAPPROVED; + $this->sql_query($sql); + + $sql = 'SELECT forum_id, topic_visibility, COUNT(topic_id) AS sum_topics, SUM(topic_posts_approved) AS sum_posts_approved, SUM(topic_posts_unapproved) AS sum_posts_unapproved + FROM ' . $this->table_prefix . 'topics + GROUP BY forum_id, topic_visibility'; + $result = $this->db->sql_query($sql); + + $update_forums = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $forum_id = (int) $row['forum_id']; + if (!isset($update_forums[$forum_id])) + { + $update_forums[$forum_id] = array( + 'forum_posts_approved' => 0, + 'forum_posts_unapproved' => 0, + 'forum_topics_approved' => 0, + 'forum_topics_unapproved' => 0, + ); + } + + $update_forums[$forum_id]['forum_posts_approved'] += (int) $row['sum_posts_approved']; + $update_forums[$forum_id]['forum_posts_unapproved'] += (int) $row['sum_posts_unapproved']; + + $update_forums[$forum_id][(($row['topic_visibility'] == ITEM_APPROVED) ? 'forum_topics_approved' : 'forum_topics_unapproved')] += (int) $row['sum_topics']; + } + $this->db->sql_freeresult($result); + + foreach ($update_forums as $forum_id => $forum_data) + { + $sql = 'UPDATE ' . FORUMS_TABLE . ' + SET ' . $this->db->sql_build_array('UPDATE', $forum_data) . ' + WHERE forum_id = ' . $forum_id; + $this->sql_query($sql); + } + } +} diff --git a/phpBB/phpbb/db/migration/data/310/softdelete_p2.php b/phpBB/phpbb/db/migration/data/310/softdelete_p2.php new file mode 100644 index 0000000000..7320a2c2bf --- /dev/null +++ b/phpBB/phpbb/db/migration/data/310/softdelete_p2.php @@ -0,0 +1,68 @@ +db_tools->sql_column_exists($this->table_prefix . 'posts', 'post_approved'); + } + + static public function depends_on() + { + return array( + 'phpbb_db_migration_data_310_dev', + 'phpbb_db_migration_data_310_softdelete_p1', + ); + } + + public function update_schema() + { + return array( + 'drop_columns' => array( + $this->table_prefix . 'forums' => array('forum_posts', 'forum_topics', 'forum_topics_real'), + $this->table_prefix . 'posts' => array('post_approved'), + $this->table_prefix . 'topics' => array('topic_approved', 'topic_replies', 'topic_replies_real'), + ), + 'drop_keys' => array( + $this->table_prefix . 'posts' => array('post_approved'), + $this->table_prefix . 'topics' => array('forum_appr_last'), + ), + ); + } + + public function revert_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'forums' => array( + 'forum_posts' => array('UINT', 0), + 'forum_topics' => array('UINT', 0), + 'forum_topics_real' => array('UINT', 0), + ), + $this->table_prefix . 'posts' => array( + 'post_approved' => array('BOOL', 1), + ), + $this->table_prefix . 'topics' => array( + 'topic_approved' => array('BOOL', 1), + 'topic_replies' => array('UINT', 0), + 'topic_replies_real' => array('UINT', 0), + ), + ), + 'add_index' => array( + $this->table_prefix . 'posts' => array( + 'post_approved' => array('post_approved'), + ), + $this->table_prefix . 'topics' => array( + 'forum_appr_last' => array('forum_id', 'topic_approved', 'topic_last_post_id'), + ), + ), + ); + } +} diff --git a/phpBB/phpbb/db/migration/data/310/style_update_p1.php b/phpBB/phpbb/db/migration/data/310/style_update_p1.php new file mode 100644 index 0000000000..d43537559d --- /dev/null +++ b/phpBB/phpbb/db/migration/data/310/style_update_p1.php @@ -0,0 +1,185 @@ +db_tools->sql_table_exists($this->table_prefix . 'styles_imageset'); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_11'); + } + + public function update_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'styles' => array( + 'style_path' => array('VCHAR:100', ''), + 'bbcode_bitfield' => array('VCHAR:255', 'kNg='), + 'style_parent_id' => array('UINT', 0), + 'style_parent_tree' => array('TEXT', ''), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_columns' => array( + $this->table_prefix . 'styles' => array( + 'style_path', + 'bbcode_bitfield', + 'style_parent_id', + 'style_parent_tree', + ), + ), + ); + } + + public function update_data() + { + return array( + array('custom', array(array($this, 'styles_update'))), + ); + } + + public function styles_update() + { + // Get list of valid 3.1 styles + $available_styles = array('prosilver'); + + $iterator = new DirectoryIterator($this->phpbb_root_path . 'styles'); + $skip_dirs = array('.', '..', 'prosilver'); + foreach ($iterator as $fileinfo) + { + if ($fileinfo->isDir() && !in_array($fileinfo->getFilename(), $skip_dirs) && file_exists($fileinfo->getPathname() . '/style.cfg')) + { + $style_cfg = parse_cfg_file($fileinfo->getPathname() . '/style.cfg'); + if (isset($style_cfg['phpbb_version']) && version_compare($style_cfg['phpbb_version'], '3.1.0-dev', '>=')) + { + // 3.1 style + $available_styles[] = $fileinfo->getFilename(); + } + } + } + + // Get all installed styles + if ($this->db_tools->sql_table_exists($this->table_prefix . 'styles_imageset')) + { + $sql = 'SELECT s.style_id, t.template_path, t.template_id, t.bbcode_bitfield, t.template_inherits_id, t.template_inherit_path, c.theme_path, c.theme_id, i.imageset_path + FROM ' . STYLES_TABLE . ' s, ' . $this->table_prefix . 'styles_template t, ' . $this->table_prefix . 'styles_theme c, ' . $this->table_prefix . "styles_imageset i + WHERE t.template_id = s.template_id + AND c.theme_id = s.theme_id + AND i.imageset_id = s.imageset_id"; + } + else + { + $sql = 'SELECT s.style_id, t.template_path, t.template_id, t.bbcode_bitfield, t.template_inherits_id, t.template_inherit_path, c.theme_path, c.theme_id + FROM ' . STYLES_TABLE . ' s, ' . $this->table_prefix . 'styles_template t, ' . $this->table_prefix . "stles_theme c + WHERE t.template_id = s.template_id + AND c.theme_id = s.theme_id"; + } + $result = $this->db->sql_query($sql); + + $styles = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $styles[] = $row; + } + $this->db->sql_freeresult($result); + + // Decide which styles to keep, all others will be deleted + $valid_styles = array(); + foreach ($styles as $style_row) + { + if ( + // Delete styles with parent style (not supported yet) + $style_row['template_inherits_id'] == 0 && + // Check if components match + $style_row['template_path'] == $style_row['theme_path'] && (!isset($style_row['imageset_path']) || $style_row['template_path'] == $style_row['imageset_path']) && + // Check if components are valid + in_array($style_row['template_path'], $available_styles) + ) + { + // Valid style. Keep it + $sql_ary = array( + 'style_path' => $style_row['template_path'], + 'bbcode_bitfield' => $style_row['bbcode_bitfield'], + 'style_parent_id' => 0, + 'style_parent_tree' => '', + ); + $this->sql_query('UPDATE ' . STYLES_TABLE . ' + SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . ' + WHERE style_id = ' . $style_row['style_id']); + $valid_styles[] = (int) $style_row['style_id']; + } + } + + // Remove old entries from styles table + if (!sizeof($valid_styles)) + { + // No valid styles: remove everything and add prosilver + $this->sql_query('DELETE FROM ' . STYLES_TABLE, $errored, $error_ary); + + $sql_ary = array( + 'style_name' => 'prosilver', + 'style_copyright' => '© phpBB Group', + 'style_active' => 1, + 'style_path' => 'prosilver', + 'bbcode_bitfield' => 'lNg=', + 'style_parent_id' => 0, + 'style_parent_tree' => '', + + // Will be removed in the next step + 'imageset_id' => 0, + 'template_id' => 0, + 'theme_id' => 0, + ); + + $sql = 'INSERT INTO ' . STYLES_TABLE . ' ' . $this->db->sql_build_array('INSERT', $sql_ary); + $this->sql_query($sql); + + $sql = 'SELECT style_id + FROM ' . $table . " + WHERE style_name = 'prosilver'"; + $result = $this->sql_query($sql); + $default_style = $this->db->sql_fetchfield($result); + $this->db->sql_freeresult($result); + + set_config('default_style', $default_style); + + $sql = 'UPDATE ' . USERS_TABLE . ' SET user_style = 0'; + $this->sql_query($sql); + } + else + { + // There are valid styles in styles table. Remove styles that are outdated + $this->sql_query('DELETE FROM ' . STYLES_TABLE . ' + WHERE ' . $this->db->sql_in_set('style_id', $valid_styles, true)); + + // Change default style + if (!in_array($this->config['default_style'], $valid_styles)) + { + $this->sql_query('UPDATE ' . CONFIG_TABLE . " + SET config_value = '" . $valid_styles[0] . "' + WHERE config_name = 'default_style'"); + } + + // Reset styles for users + $this->sql_query('UPDATE ' . USERS_TABLE . ' + SET user_style = 0 + WHERE ' . $this->db->sql_in_set('user_style', $valid_styles, true)); + } + } +} diff --git a/phpBB/phpbb/db/migration/data/310/style_update_p2.php b/phpBB/phpbb/db/migration/data/310/style_update_p2.php new file mode 100644 index 0000000000..7b10518a66 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/310/style_update_p2.php @@ -0,0 +1,129 @@ +db_tools->sql_table_exists($this->table_prefix . 'styles_imageset'); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_310_style_update_p1'); + } + + public function update_schema() + { + return array( + 'drop_columns' => array( + $this->table_prefix . 'styles' => array( + 'imageset_id', + 'template_id', + 'theme_id', + ), + ), + + 'drop_tables' => array( + $this->table_prefix . 'styles_imageset', + $this->table_prefix . 'styles_imageset_data', + $this->table_prefix . 'styles_template', + $this->table_prefix . 'styles_template_data', + $this->table_prefix . 'styles_theme', + ), + ); + } + + public function revert_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'styles' => array( + 'imageset_id' => array('UINT', 0), + 'template_id' => array('UINT', 0), + 'theme_id' => array('UINT', 0), + ), + ), + + 'add_tables' => array( + $this->table_prefix . 'styles_imageset' => array( + 'COLUMNS' => array( + 'imageset_id' => array('UINT', NULL, 'auto_increment'), + 'imageset_name' => array('VCHAR_UNI:255', ''), + 'imageset_copyright' => array('VCHAR_UNI', ''), + 'imageset_path' => array('VCHAR:100', ''), + ), + 'PRIMARY_KEY' => 'imageset_id', + 'KEYS' => array( + 'imgset_nm' => array('UNIQUE', 'imageset_name'), + ), + ), + $this->table_prefix . 'styles_imageset_data' => array( + 'COLUMNS' => array( + 'image_id' => array('UINT', NULL, 'auto_increment'), + 'image_name' => array('VCHAR:200', ''), + 'image_filename' => array('VCHAR:200', ''), + 'image_lang' => array('VCHAR:30', ''), + 'image_height' => array('USINT', 0), + 'image_width' => array('USINT', 0), + 'imageset_id' => array('UINT', 0), + ), + 'PRIMARY_KEY' => 'image_id', + 'KEYS' => array( + 'i_d' => array('INDEX', 'imageset_id'), + ), + ), + $this->table_prefix . 'styles_template' => array( + 'COLUMNS' => array( + 'template_id' => array('UINT', NULL, 'auto_increment'), + 'template_name' => array('VCHAR_UNI:255', ''), + 'template_copyright' => array('VCHAR_UNI', ''), + 'template_path' => array('VCHAR:100', ''), + 'bbcode_bitfield' => array('VCHAR:255', 'kNg='), + 'template_storedb' => array('BOOL', 0), + 'template_inherits_id' => array('UINT:4', 0), + 'template_inherit_path' => array('VCHAR', ''), + ), + 'PRIMARY_KEY' => 'template_id', + 'KEYS' => array( + 'tmplte_nm' => array('UNIQUE', 'template_name'), + ), + ), + $this->table_prefix . 'styles_template_data' => array( + 'COLUMNS' => array( + 'template_id' => array('UINT', 0), + 'template_filename' => array('VCHAR:100', ''), + 'template_included' => array('TEXT', ''), + 'template_mtime' => array('TIMESTAMP', 0), + 'template_data' => array('MTEXT_UNI', ''), + ), + 'KEYS' => array( + 'tid' => array('INDEX', 'template_id'), + 'tfn' => array('INDEX', 'template_filename'), + ), + ), + $this->table_prefix . 'styles_theme' => array( + 'COLUMNS' => array( + 'theme_id' => array('UINT', NULL, 'auto_increment'), + 'theme_name' => array('VCHAR_UNI:255', ''), + 'theme_copyright' => array('VCHAR_UNI', ''), + 'theme_path' => array('VCHAR:100', ''), + 'theme_storedb' => array('BOOL', 0), + 'theme_mtime' => array('TIMESTAMP', 0), + 'theme_data' => array('MTEXT_UNI', ''), + ), + 'PRIMARY_KEY' => 'theme_id', + 'KEYS' => array( + 'theme_name' => array('UNIQUE', 'theme_name'), + ), + ), + ), + ); + } +} diff --git a/phpBB/phpbb/db/migration/data/310/teampage.php b/phpBB/phpbb/db/migration/data/310/teampage.php new file mode 100644 index 0000000000..4e77da17b7 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/310/teampage.php @@ -0,0 +1,104 @@ +db_tools->sql_table_exists($this->table_prefix . 'teampage'); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_310_dev'); + } + + public function update_schema() + { + return array( + 'add_tables' => array( + $this->table_prefix . 'teampage' => array( + 'COLUMNS' => array( + 'teampage_id' => array('UINT', NULL, 'auto_increment'), + 'group_id' => array('UINT', 0), + 'teampage_name' => array('VCHAR_UNI:255', ''), + 'teampage_position' => array('UINT', 0), + 'teampage_parent' => array('UINT', 0), + ), + 'PRIMARY_KEY' => 'teampage_id', + ), + ), + 'drop_columns' => array( + $this->table_prefix . 'groups' => array( + 'group_teampage', + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_tables' => array( + $this->table_prefix . 'teampage', + ), + 'add_columns' => array( + $this->table_prefix . 'groups' => array( + 'group_teampage' => array('UINT', 0, 'after' => 'group_legend'), + ), + ), + ); + } + + public function update_data() + { + return array( + array('custom', array(array($this, 'add_groups_teampage'))), + ); + } + + public function add_groups_teampage() + { + $sql = 'SELECT teampage_id + FROM ' . TEAMPAGE_TABLE; + $result = $this->db->sql_query_limit($sql, 1); + $added_groups_teampage = (bool) $this->db->sql_fetchfield('teampage_id'); + $this->db->sql_freeresult($result); + + if (!$added_groups_teampage) + { + $sql = 'SELECT * + FROM ' . GROUPS_TABLE . ' + WHERE group_type = ' . GROUP_SPECIAL . " + AND (group_name = 'ADMINISTRATORS' + OR group_name = 'GLOBAL_MODERATORS') + ORDER BY group_name ASC"; + $result = $this->db->sql_query($sql); + + $teampage_entries = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $teampage_entries[] = array( + 'group_id' => (int) $row['group_id'], + 'teampage_name' => '', + 'teampage_position' => sizeof($teampage_entries) + 1, + 'teampage_parent' => 0, + ); + } + $this->db->sql_freeresult($result); + + if (sizeof($teampage_entries)) + { + $this->db->sql_multi_insert(TEAMPAGE_TABLE, $teampage_entries); + } + unset($teampage_entries); + } + + } +} diff --git a/phpBB/phpbb/db/migration/data/310/timezone.php b/phpBB/phpbb/db/migration/data/310/timezone.php new file mode 100644 index 0000000000..6e50cbe45f --- /dev/null +++ b/phpBB/phpbb/db/migration/data/310/timezone.php @@ -0,0 +1,163 @@ +db_tools->sql_column_exists($this->table_prefix . 'users', 'user_dst'); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_11'); + } + + public function update_schema() + { + return array( + 'change_columns' => array( + $this->table_prefix . 'users' => array( + 'user_timezone' => array('VCHAR:100', ''), + ), + ), + ); + } + + public function update_data() + { + return array( + array('custom', array(array($this, 'update_timezones'))), + ); + } + + public function update_timezones() + { + // Update user timezones + $sql = 'SELECT user_dst, user_timezone + FROM ' . $this->table_prefix . 'users + GROUP BY user_timezone, user_dst'; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $sql = 'UPDATE ' . $this->table_prefix . "users + SET user_timezone = '" . $this->db->sql_escape($this->convert_phpbb30_timezone($row['user_timezone'], $row['user_dst'])) . "' + WHERE user_timezone = '" . $this->db->sql_escape($row['user_timezone']) . "' + AND user_dst = " . (int) $row['user_dst']; + $this->sql_query($sql); + } + $this->db->sql_freeresult($result); + + // Update board default timezone + $sql = 'UPDATE ' . $this->table_prefix . "config + SET config_value = '" . $this->convert_phpbb30_timezone($this->config['board_timezone'], $this->config['board_dst']) . "' + WHERE config_name = 'board_timezone'"; + $this->sql_query($sql); + } + + /** + * Determine the new timezone for a given phpBB 3.0 timezone and + * "Daylight Saving Time" option + * + * @param $timezone float Users timezone in 3.0 + * @param $dst int Users daylight saving time + * @return string Users new php Timezone which is used since 3.1 + */ + public function convert_phpbb30_timezone($timezone, $dst) + { + $offset = $timezone + $dst; + + switch ($timezone) + { + case '-12': + return 'Etc/GMT+' . abs($offset); //'[UTC - 12] Baker Island Time' + case '-11': + return 'Etc/GMT+' . abs($offset); //'[UTC - 11] Niue Time, Samoa Standard Time' + case '-10': + return 'Etc/GMT+' . abs($offset); //'[UTC - 10] Hawaii-Aleutian Standard Time, Cook Island Time' + case '-9.5': + return 'Pacific/Marquesas'; //'[UTC - 9:30] Marquesas Islands Time' + case '-9': + return 'Etc/GMT+' . abs($offset); //'[UTC - 9] Alaska Standard Time, Gambier Island Time' + case '-8': + return 'Etc/GMT+' . abs($offset); //'[UTC - 8] Pacific Standard Time' + case '-7': + return 'Etc/GMT+' . abs($offset); //'[UTC - 7] Mountain Standard Time' + case '-6': + return 'Etc/GMT+' . abs($offset); //'[UTC - 6] Central Standard Time' + case '-5': + return 'Etc/GMT+' . abs($offset); //'[UTC - 5] Eastern Standard Time' + case '-4.5': + return 'America/Caracas'; //'[UTC - 4:30] Venezuelan Standard Time' + case '-4': + return 'Etc/GMT+' . abs($offset); //'[UTC - 4] Atlantic Standard Time' + case '-3.5': + return 'America/St_Johns'; //'[UTC - 3:30] Newfoundland Standard Time' + case '-3': + return 'Etc/GMT+' . abs($offset); //'[UTC - 3] Amazon Standard Time, Central Greenland Time' + case '-2': + return 'Etc/GMT+' . abs($offset); //'[UTC - 2] Fernando de Noronha Time, South Georgia & the South Sandwich Islands Time' + case '-1': + return 'Etc/GMT+' . abs($offset); //'[UTC - 1] Azores Standard Time, Cape Verde Time, Eastern Greenland Time' + case '0': + return (!$dst) ? 'UTC' : 'Etc/GMT-1'; //'[UTC] Western European Time, Greenwich Mean Time' + case '1': + return 'Etc/GMT-' . $offset; //'[UTC + 1] Central European Time, West African Time' + case '2': + return 'Etc/GMT-' . $offset; //'[UTC + 2] Eastern European Time, Central African Time' + case '3': + return 'Etc/GMT-' . $offset; //'[UTC + 3] Moscow Standard Time, Eastern African Time' + case '3.5': + return 'Asia/Tehran'; //'[UTC + 3:30] Iran Standard Time' + case '4': + return 'Etc/GMT-' . $offset; //'[UTC + 4] Gulf Standard Time, Samara Standard Time' + case '4.5': + return 'Asia/Kabul'; //'[UTC + 4:30] Afghanistan Time' + case '5': + return 'Etc/GMT-' . $offset; //'[UTC + 5] Pakistan Standard Time, Yekaterinburg Standard Time' + case '5.5': + return 'Asia/Kolkata'; //'[UTC + 5:30] Indian Standard Time, Sri Lanka Time' + case '5.75': + return 'Asia/Kathmandu'; //'[UTC + 5:45] Nepal Time' + case '6': + return 'Etc/GMT-' . $offset; //'[UTC + 6] Bangladesh Time, Bhutan Time, Novosibirsk Standard Time' + case '6.5': + return 'Indian/Cocos'; //'[UTC + 6:30] Cocos Islands Time, Myanmar Time' + case '7': + return 'Etc/GMT-' . $offset; //'[UTC + 7] Indochina Time, Krasnoyarsk Standard Time' + case '8': + return 'Etc/GMT-' . $offset; //'[UTC + 8] Chinese Standard Time, Australian Western Standard Time, Irkutsk Standard Time' + case '8.75': + return 'Australia/Eucla'; //'[UTC + 8:45] Southeastern Western Australia Standard Time' + case '9': + return 'Etc/GMT-' . $offset; //'[UTC + 9] Japan Standard Time, Korea Standard Time, Chita Standard Time' + case '9.5': + return 'Australia/ACT'; //'[UTC + 9:30] Australian Central Standard Time' + case '10': + return 'Etc/GMT-' . $offset; //'[UTC + 10] Australian Eastern Standard Time, Vladivostok Standard Time' + case '10.5': + return 'Australia/Lord_Howe'; //'[UTC + 10:30] Lord Howe Standard Time' + case '11': + return 'Etc/GMT-' . $offset; //'[UTC + 11] Solomon Island Time, Magadan Standard Time' + case '11.5': + return 'Pacific/Norfolk'; //'[UTC + 11:30] Norfolk Island Time' + case '12': + return 'Etc/GMT-12'; //'[UTC + 12] New Zealand Time, Fiji Time, Kamchatka Standard Time' + case '12.75': + return 'Pacific/Chatham'; //'[UTC + 12:45] Chatham Islands Time' + case '13': + return 'Pacific/Tongatapu'; //'[UTC + 13] Tonga Time, Phoenix Islands Time' + case '14': + return 'Pacific/Kiritimati'; //'[UTC + 14] Line Island Time' + default: + return 'UTC'; + } + } +} diff --git a/phpBB/phpbb/db/migration/data/310/timezone_p2.php b/phpBB/phpbb/db/migration/data/310/timezone_p2.php new file mode 100644 index 0000000000..113b979e4f --- /dev/null +++ b/phpBB/phpbb/db/migration/data/310/timezone_p2.php @@ -0,0 +1,43 @@ +db_tools->sql_column_exists($this->table_prefix . 'users', 'user_dst'); + } + + static public function depends_on() + { + return array('phpbb_db_migration_data_310_timezone'); + } + + public function update_schema() + { + return array( + 'drop_columns' => array( + $this->table_prefix . 'users' => array( + 'user_dst', + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'users' => array( + 'user_dst' => array('BOOL', 0), + ), + ), + ); + } +} diff --git a/phpBB/phpbb/db/migration/exception.php b/phpBB/phpbb/db/migration/exception.php new file mode 100644 index 0000000000..e84330dd71 --- /dev/null +++ b/phpBB/phpbb/db/migration/exception.php @@ -0,0 +1,79 @@ +parameters = $parameters; + } + + /** + * Output the error as a string + * + * @return string + */ + public function __toString() + { + return $this->message . ': ' . var_export($this->parameters, true); + } + + /** + * Get the parameters + * + * @return array + */ + public function getParameters() + { + return $this->parameters; + } + + /** + * Get localised message (with $user->lang()) + * + * @param phpbb_user $user + * @return string + */ + public function getLocalisedMessage(phpbb_user $user) + { + $parameters = $this->getParameters(); + array_unshift($parameters, $this->getMessage()); + + return call_user_func_array(array($user, 'lang'), $parameters); + } +} diff --git a/phpBB/phpbb/db/migration/migration.php b/phpBB/phpbb/db/migration/migration.php new file mode 100644 index 0000000000..0ffa96fd14 --- /dev/null +++ b/phpBB/phpbb/db/migration/migration.php @@ -0,0 +1,190 @@ +sql_query() */ + protected $queries = array(); + + /** + * Constructor + * + * @param phpbb_config $config + * @param phpbb_db_driver $db + * @param phpbb_db_tools $db_tools + * @param string $phpbb_root_path + * @param string $php_ext + * @param string $table_prefix + */ + public function __construct(phpbb_config $config, phpbb_db_driver $db, phpbb_db_tools $db_tools, $phpbb_root_path, $php_ext, $table_prefix) + { + $this->config = $config; + $this->db = $db; + $this->db_tools = $db_tools; + $this->table_prefix = $table_prefix; + + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + + $this->errors = array(); + } + + /** + * Defines other migrations to be applied first + * + * @return array An array of migration class names + */ + static public function depends_on() + { + return array(); + } + + /** + * Allows you to check if the migration is effectively installed (entirely optional) + * + * This is checked when a migration is installed. If true is returned, the migration will be set as + * installed without performing the database changes. + * This function is intended to help moving to migrations from a previous database updater, where some + * migrations may have been installed already even though they are not yet listed in the migrations table. + * + * @return bool True if this migration is installed, False if this migration is not installed (checked on install) + */ + public function effectively_installed() + { + return false; + } + + /** + * Updates the database schema by providing a set of change instructions + * + * @return array Array of schema changes (compatible with db_tools->perform_schema_changes()) + */ + public function update_schema() + { + return array(); + } + + /** + * Reverts the database schema by providing a set of change instructions + * + * @return array Array of schema changes (compatible with db_tools->perform_schema_changes()) + */ + public function revert_schema() + { + return array(); + } + + /** + * Updates data by returning a list of instructions to be executed + * + * @return array Array of data update instructions + */ + public function update_data() + { + return array(); + } + + /** + * Reverts data by returning a list of instructions to be executed + * + * @return array Array of data instructions that will be performed on revert + * NOTE: calls to tools (such as config.add) are automatically reverted when + * possible, so you should not attempt to revert those, this is mostly for + * otherwise unrevertable calls (custom functions for example) + */ + public function revert_data() + { + return array(); + } + + /** + * Wrapper for running queries to generate user feedback on updates + * + * @param string $sql SQL query to run on the database + * @return mixed Query result from db->sql_query() + */ + protected function sql_query($sql) + { + $this->queries[] = $sql; + + $this->db->sql_return_on_error(true); + + if ($sql === 'begin') + { + $result = $this->db->sql_transaction('begin'); + } + else if ($sql === 'commit') + { + $result = $this->db->sql_transaction('commit'); + } + else + { + $result = $this->db->sql_query($sql); + if ($this->db->sql_error_triggered) + { + $this->errors[] = array( + 'sql' => $this->db->sql_error_sql, + 'code' => $this->db->sql_error_returned, + ); + } + } + + $this->db->sql_return_on_error(false); + + return $result; + } + + /** + * Get the list of queries run + * + * @return array + */ + public function get_queries() + { + return $this->queries; + } +} diff --git a/phpBB/phpbb/db/migration/tool/config.php b/phpBB/phpbb/db/migration/tool/config.php new file mode 100644 index 0000000000..0b626bf455 --- /dev/null +++ b/phpBB/phpbb/db/migration/tool/config.php @@ -0,0 +1,150 @@ +config = $config; + } + + /** + * {@inheritdoc} + */ + public function get_name() + { + return 'config'; + } + + /** + * Add a config setting. + * + * @param string $config_name The name of the config setting + * you would like to add + * @param mixed $config_value The value of the config setting + * @param bool $is_dynamic True if it is dynamic (changes very often) + * and should not be stored in the cache, false if not. + * @return null + */ + public function add($config_name, $config_value, $is_dynamic = false) + { + if (isset($this->config[$config_name])) + { + return; + } + + $this->config->set($config_name, $config_value, !$is_dynamic); + } + + /** + * Update an existing config setting. + * + * @param string $config_name The name of the config setting you would + * like to update + * @param mixed $config_value The value of the config setting + * @return null + */ + public function update($config_name, $config_value) + { + if (!isset($this->config[$config_name])) + { + throw new phpbb_db_migration_exception('CONFIG_NOT_EXIST', $config_name); + } + + $this->config->set($config_name, $config_value); + } + + /** + * Update a config setting if the first argument equal to the + * current config value + * + * @param string $compare If equal to the current config value, will be + * updated to the new config value, otherwise not + * @param string $config_name The name of the config setting you would + * like to update + * @param mixed $config_value The value of the config setting + * @return null + */ + public function update_if_equals($compare, $config_name, $config_value) + { + if (!isset($this->config[$config_name])) + { + throw new phpbb_db_migration_exception('CONFIG_NOT_EXIST', $config_name); + } + + $this->config->set_atomic($config_name, $compare, $config_value); + } + + /** + * Remove an existing config setting. + * + * @param string $config_name The name of the config setting you would + * like to remove + * @return null + */ + public function remove($config_name) + { + if (!isset($this->config[$config_name])) + { + return; + } + + $this->config->delete($config_name); + } + + /** + * {@inheritdoc} + */ + public function reverse() + { + $arguments = func_get_args(); + $original_call = array_shift($arguments); + + $call = false; + switch ($original_call) + { + case 'add': + $call = 'remove'; + break; + + case 'remove': + $call = 'add'; + break; + + case 'update_if_equals': + $call = 'update_if_equals'; + + // Set to the original value if the current value is what we compared to originally + $arguments = array( + $arguments[2], + $arguments[1], + $arguments[0], + ); + break; + } + + if ($call) + { + return call_user_func_array(array(&$this, $call), $arguments); + } + } +} diff --git a/phpBB/phpbb/db/migration/tool/interface.php b/phpBB/phpbb/db/migration/tool/interface.php new file mode 100644 index 0000000000..ced53b2023 --- /dev/null +++ b/phpBB/phpbb/db/migration/tool/interface.php @@ -0,0 +1,33 @@ +db = $db; + $this->cache = $cache; + $this->user = $user; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + $this->modules_table = $modules_table; + } + + /** + * {@inheritdoc} + */ + public function get_name() + { + return 'module'; + } + + /** + * Module Exists + * + * Check if a module exists + * + * @param string $class The module class(acp|mcp|ucp) + * @param int|string|bool $parent The parent module_id|module_langname (0 for no parent). + * Use false to ignore the parent check and check class wide. + * @param int|string $module The module_id|module_langname you would like to + * check for to see if it exists + * @return bool true/false if module exists + */ + public function exists($class, $parent, $module) + { + // the main root directory should return true + if (!$module) + { + return true; + } + + $parent_sql = ''; + if ($parent !== false) + { + // Allows '' to be sent as 0 + $parent = $parent ?: 0; + + if (!is_numeric($parent)) + { + $sql = 'SELECT module_id + FROM ' . $this->modules_table . " + WHERE module_langname = '" . $this->db->sql_escape($parent) . "' + AND module_class = '" . $this->db->sql_escape($class) . "'"; + $result = $this->db->sql_query($sql); + $module_id = $this->db->sql_fetchfield('module_id'); + $this->db->sql_freeresult($result); + + if (!$module_id) + { + return false; + } + + $parent_sql = 'AND parent_id = ' . (int) $module_id; + } + else + { + $parent_sql = 'AND parent_id = ' . (int) $parent; + } + } + + $sql = 'SELECT module_id + FROM ' . $this->modules_table . " + WHERE module_class = '" . $this->db->sql_escape($class) . "' + $parent_sql + AND " . ((is_numeric($module)) ? 'module_id = ' . (int) $module : "module_langname = '" . $this->db->sql_escape($module) . "'"); + $result = $this->db->sql_query($sql); + $module_id = $this->db->sql_fetchfield('module_id'); + $this->db->sql_freeresult($result); + + if ($module_id) + { + return true; + } + + return false; + } + + /** + * Module Add + * + * Add a new module + * + * @param string $class The module class(acp|mcp|ucp) + * @param int|string $parent The parent module_id|module_langname (0 for no parent) + * @param array $data an array of the data on the new module. + * This can be setup in two different ways. + * 1. The "manual" way. For inserting a category or one at a time. + * It will be merged with the base array shown a bit below, + * but at the least requires 'module_langname' to be sent, and, + * if you want to create a module (instead of just a category) you must + * send module_basename and module_mode. + * array( + * 'module_enabled' => 1, + * 'module_display' => 1, + * 'module_basename' => '', + * 'module_class' => $class, + * 'parent_id' => (int) $parent, + * 'module_langname' => '', + * 'module_mode' => '', + * 'module_auth' => '', + * ) + * 2. The "automatic" way. For inserting multiple at a time based on the + * specs in the info file for the module(s). For this to work the + * modules must be correctly setup in the info file. + * An example follows (this would insert the settings, log, and flag + * modes from the includes/acp/info/acp_asacp.php file): + * array( + * 'module_basename' => 'asacp', + * 'modes' => array('settings', 'log', 'flag'), + * ) + * Optionally you may not send 'modes' and it will insert all of the + * modules in that info file. + * @param string|bool $include_path If you would like to use a custom include + * path, specify that here + * @return null + */ + public function add($class, $parent = 0, $data = array(), $include_path = false) + { + // Allows '' to be sent as 0 + $parent = $parent ?: 0; + + // allow sending the name as a string in $data to create a category + if (!is_array($data)) + { + $data = array('module_langname' => $data); + } + + if (!isset($data['module_langname'])) + { + // The "automatic" way + $basename = (isset($data['module_basename'])) ? $data['module_basename'] : ''; + $basename = str_replace(array('/', '\\'), '', $basename); + $class = str_replace(array('/', '\\'), '', $class); + + $module = $this->get_module_info($class, $basename); + + $result = ''; + foreach ($module['modes'] as $mode => $module_info) + { + if (!isset($data['modes']) || in_array($mode, $data['modes'])) + { + $new_module = array( + 'module_basename' => $basename, + 'module_langname' => $module_info['title'], + 'module_mode' => $mode, + 'module_auth' => $module_info['auth'], + 'module_display' => (isset($module_info['display'])) ? $module_info['display'] : true, + 'before' => (isset($module_info['before'])) ? $module_info['before'] : false, + 'after' => (isset($module_info['after'])) ? $module_info['after'] : false, + ); + + // Run the "manual" way with the data we've collected. + $this->add($class, $parent, $new_module); + } + } + + return; + } + + // The "manual" way + if (!is_numeric($parent)) + { + $sql = 'SELECT module_id + FROM ' . $this->modules_table . " + WHERE module_langname = '" . $this->db->sql_escape($parent) . "' + AND module_class = '" . $this->db->sql_escape($class) . "'"; + $result = $this->db->sql_query($sql); + $module_id = $this->db->sql_fetchfield('module_id'); + $this->db->sql_freeresult($result); + + if (!$module_id) + { + throw new phpbb_db_migration_exception('MODULE_NOT_EXIST', $parent); + } + + $parent = $data['parent_id'] = $module_id; + } + else if (!$this->exists($class, false, $parent)) + { + throw new phpbb_db_migration_exception('MODULE_NOT_EXIST', $parent); + } + + if ($this->exists($class, $parent, $data['module_langname'])) + { + return; + } + + if (!class_exists('acp_modules')) + { + include($this->phpbb_root_path . 'includes/acp/acp_modules.' . $this->php_ext); + $this->user->add_lang('acp/modules'); + } + $acp_modules = new acp_modules(); + + $module_data = array( + 'module_enabled' => (isset($data['module_enabled'])) ? $data['module_enabled'] : 1, + 'module_display' => (isset($data['module_display'])) ? $data['module_display'] : 1, + 'module_basename' => (isset($data['module_basename'])) ? $data['module_basename'] : '', + 'module_class' => $class, + 'parent_id' => (int) $parent, + 'module_langname' => (isset($data['module_langname'])) ? $data['module_langname'] : '', + 'module_mode' => (isset($data['module_mode'])) ? $data['module_mode'] : '', + 'module_auth' => (isset($data['module_auth'])) ? $data['module_auth'] : '', + ); + $result = $acp_modules->update_module_data($module_data, true); + + // update_module_data can either return a string or an empty array... + if (is_string($result)) + { + // Error + throw new phpbb_db_migration_exception('MODULE_ERROR', $result); + } + else + { + // Success + $module_log_name = ((isset($this->user->lang[$data['module_langname']])) ? $this->user->lang[$data['module_langname']] : $data['module_langname']); + add_log('admin', 'LOG_MODULE_ADD', $module_log_name); + + // Move the module if requested above/below an existing one + if (isset($data['before']) && $data['before']) + { + $sql = 'SELECT left_id + FROM ' . $this->modules_table . " + WHERE module_class = '" . $this->db->sql_escape($class) . "' + AND parent_id = " . (int) $parent . " + AND module_langname = '" . $this->db->sql_escape($data['before']) . "'"; + $this->db->sql_query($sql); + $to_left = (int) $this->db->sql_fetchfield('left_id'); + + $sql = 'UPDATE ' . $this->modules_table . " + SET left_id = left_id + 2, right_id = right_id + 2 + WHERE module_class = '" . $this->db->sql_escape($class) . "' + AND left_id >= $to_left + AND left_id < {$module_data['left_id']}"; + $this->db->sql_query($sql); + + $sql = 'UPDATE ' . $this->modules_table . " + SET left_id = $to_left, right_id = " . ($to_left + 1) . " + WHERE module_class = '" . $this->db->sql_escape($class) . "' + AND module_id = {$module_data['module_id']}"; + $this->db->sql_query($sql); + } + else if (isset($data['after']) && $data['after']) + { + $sql = 'SELECT right_id + FROM ' . $this->modules_table . " + WHERE module_class = '" . $this->db->sql_escape($class) . "' + AND parent_id = " . (int) $parent . " + AND module_langname = '" . $this->db->sql_escape($data['after']) . "'"; + $this->db->sql_query($sql); + $to_right = (int) $this->db->sql_fetchfield('right_id'); + + $sql = 'UPDATE ' . $this->modules_table . " + SET left_id = left_id + 2, right_id = right_id + 2 + WHERE module_class = '" . $this->db->sql_escape($class) . "' + AND left_id >= $to_right + AND left_id < {$module_data['left_id']}"; + $this->db->sql_query($sql); + + $sql = 'UPDATE ' . $this->modules_table . ' + SET left_id = ' . ($to_right + 1) . ', right_id = ' . ($to_right + 2) . " + WHERE module_class = '" . $this->db->sql_escape($class) . "' + AND module_id = {$module_data['module_id']}"; + $this->db->sql_query($sql); + } + } + + // Clear the Modules Cache + $this->cache->destroy("_modules_$class"); + } + + /** + * Module Remove + * + * Remove a module + * + * @param string $class The module class(acp|mcp|ucp) + * @param int|string|bool $parent The parent module_id|module_langname(0 for no parent). + * Use false to ignore the parent check and check class wide. + * @param int|string $module The module id|module_langname + * @param string|bool $include_path If you would like to use a custom include path, + * specify that here + * @return null + */ + public function remove($class, $parent = 0, $module = '', $include_path = false) + { + // Imitation of module_add's "automatic" and "manual" method so the uninstaller works from the same set of instructions for umil_auto + if (is_array($module)) + { + if (isset($module['module_langname'])) + { + // Manual Method + return $this->remove($class, $parent, $module['module_langname'], $include_path); + } + + // Failed. + if (!isset($module['module_basename'])) + { + throw new phpbb_db_migration_exception('MODULE_NOT_EXIST'); + } + + // Automatic method + $basename = str_replace(array('/', '\\'), '', $module['module_basename']); + $class = str_replace(array('/', '\\'), '', $class); + + $module_info = $this->get_module_info($class, $basename); + + foreach ($module_info['modes'] as $mode => $info) + { + if (!isset($module['modes']) || in_array($mode, $module['modes'])) + { + $this->remove($class, $parent, $info['title']); + } + } + } + else + { + if (!$this->exists($class, $parent, $module)) + { + return; + } + + $parent_sql = ''; + if ($parent !== false) + { + // Allows '' to be sent as 0 + $parent = ($parent) ?: 0; + + if (!is_numeric($parent)) + { + $sql = 'SELECT module_id + FROM ' . $this->modules_table . " + WHERE module_langname = '" . $this->db->sql_escape($parent) . "' + AND module_class = '" . $this->db->sql_escape($class) . "'"; + $result = $this->db->sql_query($sql); + $module_id = $this->db->sql_fetchfield('module_id'); + $this->db->sql_freeresult($result); + + // we know it exists from the module_exists check + $parent_sql = 'AND parent_id = ' . (int) $module_id; + } + else + { + $parent_sql = 'AND parent_id = ' . (int) $parent; + } + } + + $module_ids = array(); + if (!is_numeric($module)) + { + $sql = 'SELECT module_id + FROM ' . $this->modules_table . " + WHERE module_langname = '" . $this->db->sql_escape($module) . "' + AND module_class = '" . $this->db->sql_escape($class) . "' + $parent_sql"; + $result = $this->db->sql_query($sql); + while ($module_id = $this->db->sql_fetchfield('module_id')) + { + $module_ids[] = (int) $module_id; + } + $this->db->sql_freeresult($result); + + $module_name = $module; + } + else + { + $module = (int) $module; + $sql = 'SELECT module_langname + FROM ' . $this->modules_table . " + WHERE module_id = $module + AND module_class = '" . $this->db->sql_escape($class) . "' + $parent_sql"; + $result = $this->db->sql_query($sql); + $module_name = $this->db->sql_fetchfield('module_id'); + $this->db->sql_freeresult($result); + + $module_ids[] = $module; + } + + if (!class_exists('acp_modules')) + { + include($this->phpbb_root_path . 'includes/acp/acp_modules.' . $this->php_ext); + $this->user->add_lang('acp/modules'); + } + $acp_modules = new acp_modules(); + $acp_modules->module_class = $class; + + foreach ($module_ids as $module_id) + { + $result = $acp_modules->delete_module($module_id); + if (!empty($result)) + { + return; + } + } + + $this->cache->destroy("_modules_$class"); + } + } + + /** + * {@inheritdoc} + */ + public function reverse() + { + $arguments = func_get_args(); + $original_call = array_shift($arguments); + + $call = false; + switch ($original_call) + { + case 'add': + $call = 'remove'; + break; + + case 'remove': + $call = 'add'; + break; + } + + if ($call) + { + return call_user_func_array(array(&$this, $call), $arguments); + } + } + + /** + * Wrapper for acp_modules::get_module_infos() + * + * @param string $class Module Class + * @param string $basename Module Basename + * @return array Module Information + */ + protected function get_module_info($class, $basename) + { + if (!class_exists('acp_modules')) + { + include($this->phpbb_root_path . 'includes/acp/acp_modules.' . $this->php_ext); + } + $acp_modules = new acp_modules(); + $module = $acp_modules->get_module_infos($basename, $class, true); + + if (empty($module)) + { + throw new phpbb_db_migration_exception('MODULE_INFO_FILE_NOT_EXIST', $class, $basename); + } + + return array_pop($module); + } +} diff --git a/phpBB/phpbb/db/migration/tool/permission.php b/phpBB/phpbb/db/migration/tool/permission.php new file mode 100644 index 0000000000..2f09c0ac72 --- /dev/null +++ b/phpBB/phpbb/db/migration/tool/permission.php @@ -0,0 +1,622 @@ +db = $db; + $this->cache = $cache; + $this->auth = $auth; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + } + + /** + * {@inheritdoc} + */ + public function get_name() + { + return 'permission'; + } + + /** + * Permission Exists + * + * Check if a permission (auth) setting exists + * + * @param string $auth_option The name of the permission (auth) option + * @param bool $global True for checking a global permission setting, + * False for a local permission setting + * @return bool true if it exists, false if not + */ + public function exists($auth_option, $global = true) + { + if ($global) + { + $type_sql = ' AND is_global = 1'; + } + else + { + $type_sql = ' AND is_local = 1'; + } + + $sql = 'SELECT auth_option_id + FROM ' . ACL_OPTIONS_TABLE . " + WHERE auth_option = '" . $this->db->sql_escape($auth_option) . "'" + . $type_sql; + $result = $this->db->sql_query($sql); + + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if ($row) + { + return true; + } + + return false; + } + + /** + * Permission Add + * + * Add a permission (auth) option + * + * @param string $auth_option The name of the permission (auth) option + * @param bool $global True for checking a global permission setting, + * False for a local permission setting + * @return null + */ + public function add($auth_option, $global = true, $copy_from = false) + { + if ($this->exists($auth_option, $global)) + { + return; + } + + // We've added permissions, so set to true to notify the user. + $this->permissions_added = true; + + if (!class_exists('auth_admin')) + { + include($this->phpbb_root_path . 'includes/acp/auth.' . $this->php_ext); + } + $auth_admin = new auth_admin(); + + // We have to add a check to see if the !$global (if global, local, and if local, global) permission already exists. If it does, acl_add_option currently has a bug which would break the ACL system, so we are having a work-around here. + if ($this->exists($auth_option, !$global)) + { + $sql_ary = array( + 'is_global' => 1, + 'is_local' => 1, + ); + $sql = 'UPDATE ' . ACL_OPTIONS_TABLE . ' + SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . " + WHERE auth_option = '" . $this->db->sql_escape($auth_option) . "'"; + $this->db->sql_query($sql); + } + else + { + if ($global) + { + $auth_admin->acl_add_option(array('global' => array($auth_option))); + } + else + { + $auth_admin->acl_add_option(array('local' => array($auth_option))); + } + } + + // The permission has been added, now we can copy it if needed + if ($copy_from && isset($auth_admin->acl_options['id'][$copy_from])) + { + $old_id = $auth_admin->acl_options['id'][$copy_from]; + $new_id = $auth_admin->acl_options['id'][$auth_option]; + + $tables = array(ACL_GROUPS_TABLE, ACL_ROLES_DATA_TABLE, ACL_USERS_TABLE); + + foreach ($tables as $table) + { + $sql = 'SELECT * + FROM ' . $table . ' + WHERE auth_option_id = ' . $old_id; + $result = $this->db->sql_query($sql); + + $sql_ary = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $row['auth_option_id'] = $new_id; + $sql_ary[] = $row; + } + $this->db->sql_freeresult($result); + + if (!empty($sql_ary)) + { + $this->db->sql_multi_insert($table, $sql_ary); + } + } + + $auth_admin->acl_clear_prefetch(); + } + } + + /** + * Permission Remove + * + * Remove a permission (auth) option + * + * @param string $auth_option The name of the permission (auth) option + * @param bool $global True for checking a global permission setting, + * False for a local permission setting + * @return null + */ + public function remove($auth_option, $global = true) + { + if (!$this->exists($auth_option, $global)) + { + return; + } + + if ($global) + { + $type_sql = ' AND is_global = 1'; + } + else + { + $type_sql = ' AND is_local = 1'; + } + $sql = 'SELECT auth_option_id, is_global, is_local + FROM ' . ACL_OPTIONS_TABLE . " + WHERE auth_option = '" . $this->db->sql_escape($auth_option) . "'" . + $type_sql; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + $id = (int) $row['auth_option_id']; + + // If it is a local and global permission, do not remove the row! :P + if ($row['is_global'] && $row['is_local']) + { + $sql = 'UPDATE ' . ACL_OPTIONS_TABLE . ' + SET ' . (($global) ? 'is_global = 0' : 'is_local = 0') . ' + WHERE auth_option_id = ' . $id; + $this->db->sql_query($sql); + } + else + { + // Delete time + $tables = array(ACL_GROUPS_TABLE, ACL_ROLES_DATA_TABLE, ACL_USERS_TABLE, ACL_OPTIONS_TABLE); + foreach ($tables as $table) + { + $this->db->sql_query('DELETE FROM ' . $table . ' + WHERE auth_option_id = ' . $id); + } + } + + // Purge the auth cache + $this->cache->destroy('_acl_options'); + $this->auth->acl_clear_prefetch(); + } + + /** + * Add a new permission role + * + * @param string $role_name The new role name + * @param sting $role_type The type (u_, m_, a_) + * @return null + */ + public function role_add($role_name, $role_type, $role_description = '') + { + $sql = 'SELECT role_id + FROM ' . ACL_ROLES_TABLE . " + WHERE role_name = '" . $this->db->sql_escape($role_name) . "'"; + $this->db->sql_query($sql); + $role_id = (int) $this->db->sql_fetchfield('role_id'); + + if ($role_id) + { + return; + } + + $sql = 'SELECT MAX(role_order) AS max_role_order + FROM ' . ACL_ROLES_TABLE . " + WHERE role_type = '" . $this->db->sql_escape($role_type) . "'"; + $this->db->sql_query($sql); + $role_order = (int) $this->db->sql_fetchfield('max_role_order'); + $role_order = (!$role_order) ? 1 : $role_order + 1; + + $sql_ary = array( + 'role_name' => $role_name, + 'role_description' => $role_description, + 'role_type' => $role_type, + 'role_order' => $role_order, + ); + + $sql = 'INSERT INTO ' . ACL_ROLES_TABLE . ' ' . $this->db->sql_build_array('INSERT', $sql_ary); + $this->db->sql_query($sql); + } + + /** + * Update the name on a permission role + * + * @param string $old_role_name The old role name + * @param string $new_role_name The new role name + * @return null + */ + public function role_update($old_role_name, $new_role_name) + { + $sql = 'SELECT role_id + FROM ' . ACL_ROLES_TABLE . " + WHERE role_name = '" . $this->db->sql_escape($old_role_name) . "'"; + $this->db->sql_query($sql); + $role_id = (int) $this->db->sql_fetchfield('role_id'); + + if (!$role_id) + { + throw new phpbb_db_migration_exception('ROLE_NOT_EXIST', $old_role_name); + } + + $sql = 'UPDATE ' . ACL_ROLES_TABLE . " + SET role_name = '" . $this->db->sql_escape($new_role_name) . "' + WHERE role_name = '" . $this->db->sql_escape($old_role_name) . "'"; + $this->db->sql_query($sql); + } + + /** + * Remove a permission role + * + * @param string $role_name The role name to remove + * @return null + */ + public function role_remove($role_name) + { + $sql = 'SELECT role_id + FROM ' . ACL_ROLES_TABLE . " + WHERE role_name = '" . $this->db->sql_escape($role_name) . "'"; + $this->db->sql_query($sql); + $role_id = (int) $this->db->sql_fetchfield('role_id'); + + if (!$role_id) + { + return; + } + + $sql = 'DELETE FROM ' . ACL_ROLES_DATA_TABLE . ' + WHERE role_id = ' . $role_id; + $this->db->sql_query($sql); + + $sql = 'DELETE FROM ' . ACL_ROLES_TABLE . ' + WHERE role_id = ' . $role_id; + $this->db->sql_query($sql); + + $this->auth->acl_clear_prefetch(); + } + + /** + * Permission Set + * + * Allows you to set permissions for a certain group/role + * + * @param string $name The name of the role/group + * @param string|array $auth_option The auth_option or array of + * auth_options you would like to set + * @param string $type The type (role|group) + * @param bool $has_permission True if you want to give them permission, + * false if you want to deny them permission + * @return null + */ + public function permission_set($name, $auth_option, $type = 'role', $has_permission = true) + { + if (!is_array($auth_option)) + { + $auth_option = array($auth_option); + } + + $new_auth = array(); + $sql = 'SELECT auth_option_id + FROM ' . ACL_OPTIONS_TABLE . ' + WHERE ' . $this->db->sql_in_set('auth_option', $auth_option); + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $new_auth[] = (int) $row['auth_option_id']; + } + $this->db->sql_freeresult($result); + + if (empty($new_auth)) + { + return; + } + + $current_auth = array(); + + $type = (string) $type; // Prevent PHP bug. + + switch ($type) + { + case 'role': + $sql = 'SELECT role_id + FROM ' . ACL_ROLES_TABLE . " + WHERE role_name = '" . $this->db->sql_escape($name) . "'"; + $this->db->sql_query($sql); + $role_id = (int) $this->db->sql_fetchfield('role_id'); + + if (!$role_id) + { + throw new phpbb_db_migration_exception('ROLE_NOT_EXIST', $name); + } + + $sql = 'SELECT auth_option_id, auth_setting + FROM ' . ACL_ROLES_DATA_TABLE . ' + WHERE role_id = ' . $role_id; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $current_auth[$row['auth_option_id']] = $row['auth_setting']; + } + $this->db->sql_freeresult($result); + break; + + case 'group': + $sql = 'SELECT group_id + FROM ' . GROUPS_TABLE . " + WHERE group_name = '" . $this->db->sql_escape($name) . "'"; + $this->db->sql_query($sql); + $group_id = (int) $this->db->sql_fetchfield('group_id'); + + if (!$group_id) + { + throw new phpbb_db_migration_exception('GROUP_NOT_EXIST', $name); + } + + // If the group has a role set for them we will add the requested permissions to that role. + $sql = 'SELECT auth_role_id + FROM ' . ACL_GROUPS_TABLE . ' + WHERE group_id = ' . $group_id . ' + AND auth_role_id <> 0 + AND forum_id = 0'; + $this->db->sql_query($sql); + $role_id = (int) $this->db->sql_fetchfield('auth_role_id'); + if ($role_id) + { + $sql = 'SELECT role_name + FROM ' . ACL_ROLES_TABLE . ' + WHERE role_id = ' . $role_id; + $this->db->sql_query($sql); + $role_name = $this->db->sql_fetchfield('role_name'); + + return $this->permission_set($role_name, $auth_option, 'role', $has_permission); + } + + $sql = 'SELECT auth_option_id, auth_setting + FROM ' . ACL_GROUPS_TABLE . ' + WHERE group_id = ' . $group_id; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $current_auth[$row['auth_option_id']] = $row['auth_setting']; + } + $this->db->sql_freeresult($result); + break; + } + + $sql_ary = array(); + switch ($type) + { + case 'role': + foreach ($new_auth as $auth_option_id) + { + if (!isset($current_auth[$auth_option_id])) + { + $sql_ary[] = array( + 'role_id' => $role_id, + 'auth_option_id' => $auth_option_id, + 'auth_setting' => $has_permission, + ); + } + } + + $this->db->sql_multi_insert(ACL_ROLES_DATA_TABLE, $sql_ary); + break; + + case 'group': + foreach ($new_auth as $auth_option_id) + { + if (!isset($current_auth[$auth_option_id])) + { + $sql_ary[] = array( + 'group_id' => $group_id, + 'auth_option_id' => $auth_option_id, + 'auth_setting' => $has_permission, + ); + } + } + + $this->db->sql_multi_insert(ACL_GROUPS_TABLE, $sql_ary); + break; + } + + $this->auth->acl_clear_prefetch(); + } + + /** + * Permission Unset + * + * Allows you to unset (remove) permissions for a certain group/role + * + * @param string $name The name of the role/group + * @param string|array $auth_option The auth_option or array of + * auth_options you would like to set + * @param string $type The type (role|group) + * @return null + */ + public function permission_unset($name, $auth_option, $type = 'role') + { + if (!is_array($auth_option)) + { + $auth_option = array($auth_option); + } + + $to_remove = array(); + $sql = 'SELECT auth_option_id + FROM ' . ACL_OPTIONS_TABLE . ' + WHERE ' . $this->db->sql_in_set('auth_option', $auth_option); + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $to_remove[] = (int) $row['auth_option_id']; + } + $this->db->sql_freeresult($result); + + if (empty($to_remove)) + { + return; + } + + $type = (string) $type; // Prevent PHP bug. + + switch ($type) + { + case 'role': + $sql = 'SELECT role_id + FROM ' . ACL_ROLES_TABLE . " + WHERE role_name = '" . $this->db->sql_escape($name) . "'"; + $this->db->sql_query($sql); + $role_id = (int) $this->db->sql_fetchfield('role_id'); + + if (!$role_id) + { + throw new phpbb_db_migration_exception('ROLE_NOT_EXIST', $name); + } + + $sql = 'DELETE FROM ' . ACL_ROLES_DATA_TABLE . ' + WHERE ' . $this->db->sql_in_set('auth_option_id', $to_remove); + $this->db->sql_query($sql); + break; + + case 'group': + $sql = 'SELECT group_id + FROM ' . GROUPS_TABLE . " + WHERE group_name = '" . $this->db->sql_escape($name) . "'"; + $this->db->sql_query($sql); + $group_id = (int) $this->db->sql_fetchfield('group_id'); + + if (!$group_id) + { + throw new phpbb_db_migration_exception('GROUP_NOT_EXIST', $name); + } + + // If the group has a role set for them we will remove the requested permissions from that role. + $sql = 'SELECT auth_role_id + FROM ' . ACL_GROUPS_TABLE . ' + WHERE group_id = ' . $group_id . ' + AND auth_role_id <> 0'; + $this->db->sql_query($sql); + $role_id = (int) $this->db->sql_fetchfield('auth_role_id'); + if ($role_id) + { + $sql = 'SELECT role_name + FROM ' . ACL_ROLES_TABLE . ' + WHERE role_id = ' . $role_id; + $this->db->sql_query($sql); + $role_name = $this->db->sql_fetchfield('role_name'); + + return $this->permission_unset($role_name, $auth_option, 'role'); + } + + $sql = 'DELETE FROM ' . ACL_GROUPS_TABLE . ' + WHERE ' . $this->db->sql_in_set('auth_option_id', $to_remove); + $this->db->sql_query($sql); + break; + } + + $this->auth->acl_clear_prefetch(); + } + + /** + * {@inheritdoc} + */ + public function reverse() + { + $arguments = func_get_args(); + $original_call = array_shift($arguments); + + $call = false; + switch ($original_call) + { + case 'add': + $call = 'remove'; + break; + + case 'remove': + $call = 'add'; + break; + + case 'permission_set': + $call = 'permission_unset'; + break; + + case 'permission_unset': + $call = 'permission_set'; + break; + + case 'role_add': + $call = 'role_remove'; + break; + + case 'role_remove': + $call = 'role_add'; + break; + + case 'role_update': + // Set to the original value if the current value is what we compared to originally + $arguments = array( + $arguments[1], + $arguments[0], + ); + break; + } + + if ($call) + { + return call_user_func_array(array(&$this, $call), $arguments); + } + } +} diff --git a/phpBB/phpbb/db/migrator.php b/phpBB/phpbb/db/migrator.php new file mode 100644 index 0000000000..ca3ffc8043 --- /dev/null +++ b/phpBB/phpbb/db/migrator.php @@ -0,0 +1,746 @@ +config = $config; + $this->db = $db; + $this->db_tools = $db_tools; + + $this->migrations_table = $migrations_table; + + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + + $this->table_prefix = $table_prefix; + + foreach ($tools as $tool) + { + $this->tools[$tool->get_name()] = $tool; + } + + $this->load_migration_state(); + } + + /** + * Loads all migrations and their application state from the database. + * + * @return null + */ + public function load_migration_state() + { + $this->migration_state = array(); + + // prevent errors in case the table does not exist yet + $this->db->sql_return_on_error(true); + + $sql = "SELECT * + FROM " . $this->migrations_table; + $result = $this->db->sql_query($sql); + + if (!$this->db->sql_error_triggered) + { + while ($migration = $this->db->sql_fetchrow($result)) + { + $this->migration_state[$migration['migration_name']] = $migration; + + $this->migration_state[$migration['migration_name']]['migration_depends_on'] = unserialize($migration['migration_depends_on']); + } + } + + $this->db->sql_freeresult($result); + + $this->db->sql_return_on_error(false); + } + + /** + * Sets the list of available migration class names to the given array. + * + * @param array $class_names An array of migration class names + * @return null + */ + public function set_migrations($class_names) + { + $this->migrations = $class_names; + } + + /** + * Runs a single update step from the next migration to be applied. + * + * The update step can either be a schema or a (partial) data update. To + * check if update() needs to be called again use the finished() method. + * + * @return null + */ + public function update() + { + foreach ($this->migrations as $name) + { + if (!isset($this->migration_state[$name]) || + !$this->migration_state[$name]['migration_schema_done'] || + !$this->migration_state[$name]['migration_data_done']) + { + if (!$this->try_apply($name)) + { + continue; + } + else + { + return; + } + } + } + } + + /** + * Attempts to apply a step of the given migration or one of its dependencies + * + * @param string The class name of the migration + * @return bool Whether any update step was successfully run + */ + protected function try_apply($name) + { + if (!class_exists($name)) + { + return false; + } + + $migration = $this->get_migration($name); + + $state = (isset($this->migration_state[$name])) ? + $this->migration_state[$name] : + array( + 'migration_depends_on' => $migration->depends_on(), + 'migration_schema_done' => false, + 'migration_data_done' => false, + 'migration_data_state' => '', + 'migration_start_time' => 0, + 'migration_end_time' => 0, + ); + + foreach ($state['migration_depends_on'] as $depend) + { + if (!isset($this->migration_state[$depend]) || + !$this->migration_state[$depend]['migration_schema_done'] || + !$this->migration_state[$depend]['migration_data_done']) + { + return $this->try_apply($depend); + } + } + + $this->last_run_migration = array( + 'name' => $name, + 'class' => $migration, + 'state' => $state, + ); + + if (!isset($this->migration_state[$name])) + { + if ($migration->effectively_installed()) + { + $state = array( + 'migration_depends_on' => $migration->depends_on(), + 'migration_schema_done' => true, + 'migration_data_done' => true, + 'migration_data_state' => '', + 'migration_start_time' => 0, + 'migration_end_time' => 0, + ); + + $this->last_run_migration['effectively_installed'] = true; + } + else + { + $state['migration_start_time'] = time(); + } + } + + if (!$state['migration_schema_done']) + { + $this->apply_schema_changes($migration->update_schema()); + $state['migration_schema_done'] = true; + } + else if (!$state['migration_data_done']) + { + try + { + $result = $this->process_data_step($migration->update_data(), $state['migration_data_state']); + + $state['migration_data_state'] = ($result === true) ? '' : $result; + $state['migration_data_done'] = ($result === true); + $state['migration_end_time'] = ($result === true) ? time() : 0; + } + catch (phpbb_db_migration_exception $e) + { + // Revert the schema changes + $this->revert($name); + + // Rethrow exception + throw $e; + } + } + + $this->set_migration_state($name, $state); + + return true; + } + + /** + * Runs a single revert step from the last migration installed + * + * YOU MUST ADD/SET ALL MIGRATIONS THAT COULD BE DEPENDENT ON THE MIGRATION TO REVERT TO BEFORE CALLING THIS METHOD! + * The revert step can either be a schema or a (partial) data revert. To + * check if revert() needs to be called again use the migration_state() method. + * + * @param string $migration String migration name to revert (including any that depend on this migration) + * @return null + */ + public function revert($migration) + { + if (!isset($this->migration_state[$migration])) + { + // Not installed + return; + } + + foreach ($this->migration_state as $name => $state) + { + if (!empty($state['migration_depends_on']) && in_array($migration, $state['migration_depends_on'])) + { + $this->revert($name); + } + } + + $this->try_revert($migration); + } + + /** + * Attempts to revert a step of the given migration or one of its dependencies + * + * @param string The class name of the migration + * @return bool Whether any update step was successfully run + */ + protected function try_revert($name) + { + if (!class_exists($name)) + { + return false; + } + + $migration = $this->get_migration($name); + + $state = $this->migration_state[$name]; + + $this->last_run_migration = array( + 'name' => $name, + 'class' => $migration, + ); + + if ($state['migration_data_done']) + { + if ($state['migration_data_state'] !== 'revert_data') + { + $result = $this->process_data_step($migration->update_data(), $state['migration_data_state'], true); + + $state['migration_data_state'] = ($result === true) ? 'revert_data' : $result; + } + else + { + $result = $this->process_data_step($migration->revert_data(), '', false); + + $state['migration_data_state'] = ($result === true) ? '' : $result; + $state['migration_data_done'] = ($result === true) ? false : true; + } + + $this->set_migration_state($name, $state); + } + else + { + $this->apply_schema_changes($migration->revert_schema()); + + $sql = 'DELETE FROM ' . $this->migrations_table . " + WHERE migration_name = '" . $this->db->sql_escape($name) . "'"; + $this->db->sql_query($sql); + + unset($this->migration_state[$name]); + } + + return true; + } + + /** + * Apply schema changes from a migration + * + * Just calls db_tools->perform_schema_changes + * + * @param array $schema_changes from migration + */ + protected function apply_schema_changes($schema_changes) + { + $this->db_tools->perform_schema_changes($schema_changes); + } + + /** + * Process the data step of the migration + * + * @param array $steps The steps to run + * @param bool|string $state Current state of the migration + * @param bool $revert true to revert a data step + * @return bool|string migration state. True if completed, serialized array if not finished + */ + protected function process_data_step($steps, $state, $revert = false) + { + $state = ($state) ? unserialize($state) : false; + + // reverse order of steps if reverting + if ($revert === true) + { + $steps = array_reverse($steps); + } + + foreach ($steps as $step_identifier => $step) + { + $last_result = false; + if ($state) + { + // Continue until we reach the step that matches the last step called + if ($state['step'] != $step_identifier) + { + continue; + } + + // We send the result from last time to the callable function + $last_result = $state['result']; + + // Set state to false since we reached the point we were at + $state = false; + } + + try + { + // Result will be null or true if everything completed correctly + $result = $this->run_step($step, $last_result, $revert); + if ($result !== null && $result !== true) + { + return serialize(array( + 'result' => $result, + 'step' => $step_identifier, + )); + } + } + catch (phpbb_db_migration_exception $e) + { + // We should try rolling back here + foreach ($steps as $reverse_step_identifier => $reverse_step) + { + // If we've reached the current step we can break because we reversed everything that was run + if ($reverse_step_identifier == $step_identifier) + { + break; + } + + // Reverse the step that was run + $result = $this->run_step($reverse_step, false, !$revert); + } + + // rethrow the exception + throw $e; + } + } + + return true; + } + + /** + * Run a single step + * + * An exception should be thrown if an error occurs + * + * @param mixed $step Data step from migration + * @param mixed $last_result Result to pass to the callable (only for 'custom' method) + * @param bool $reverse False to install, True to attempt uninstallation by reversing the call + * @return null + */ + protected function run_step($step, $last_result = false, $reverse = false) + { + $callable_and_parameters = $this->get_callable_from_step($step, $last_result, $reverse); + + if ($callable_and_parameters === false) + { + return; + } + + $callable = $callable_and_parameters[0]; + $parameters = $callable_and_parameters[1]; + + return call_user_func_array($callable, $parameters); + } + + /** + * Get a callable statement from a data step + * + * @param array $step Data step from migration + * @param mixed $last_result Result to pass to the callable (only for 'custom' method) + * @param bool $reverse False to install, True to attempt uninstallation by reversing the call + * @return array Array with parameters for call_user_func_array(), 0 is the callable, 1 is parameters + */ + protected function get_callable_from_step(array $step, $last_result = false, $reverse = false) + { + $type = $step[0]; + $parameters = $step[1]; + + $parts = explode('.', $type); + + $class = $parts[0]; + $method = false; + + if (isset($parts[1])) + { + $method = $parts[1]; + } + + switch ($class) + { + case 'if': + if (!isset($parameters[0])) + { + throw new phpbb_db_migration_exception('MIGRATION_INVALID_DATA_MISSING_CONDITION', $step); + } + + if (!isset($parameters[1])) + { + throw new phpbb_db_migration_exception('MIGRATION_INVALID_DATA_MISSING_STEP', $step); + } + + $condition = $parameters[0]; + + if (!$condition) + { + return false; + } + + $step = $parameters[1]; + + return $this->get_callable_from_step($step); + break; + case 'custom': + if (!is_callable($parameters[0])) + { + throw new phpbb_db_migration_exception('MIGRATION_INVALID_DATA_CUSTOM_NOT_CALLABLE', $step); + } + + return array( + $parameters[0], + array($last_result), + ); + break; + + default: + if (!$method) + { + throw new phpbb_db_migration_exception('MIGRATION_INVALID_DATA_UNKNOWN_TYPE', $step); + } + + if (!isset($this->tools[$class])) + { + throw new phpbb_db_migration_exception('MIGRATION_INVALID_DATA_UNDEFINED_TOOL', $step); + } + + if (!method_exists(get_class($this->tools[$class]), $method)) + { + throw new phpbb_db_migration_exception('MIGRATION_INVALID_DATA_UNDEFINED_METHOD', $step); + } + + // Attempt to reverse operations + if ($reverse) + { + array_unshift($parameters, $method); + + return array( + array($this->tools[$class], 'reverse'), + $parameters, + ); + } + + return array( + array($this->tools[$class], $method), + $parameters, + ); + break; + } + } + + /** + * Insert/Update migration row into the database + * + * @param string $name Name of the migration + * @param array $state + * @return null + */ + protected function set_migration_state($name, $state) + { + $migration_row = $state; + $migration_row['migration_depends_on'] = serialize($state['migration_depends_on']); + + if (isset($this->migration_state[$name])) + { + $sql = 'UPDATE ' . $this->migrations_table . ' + SET ' . $this->db->sql_build_array('UPDATE', $migration_row) . " + WHERE migration_name = '" . $this->db->sql_escape($name) . "'"; + $this->db->sql_query($sql); + } + else + { + $migration_row['migration_name'] = $name; + $sql = 'INSERT INTO ' . $this->migrations_table . ' + ' . $this->db->sql_build_array('INSERT', $migration_row); + $this->db->sql_query($sql); + } + + $this->migration_state[$name] = $state; + + $this->last_run_migration['state'] = $state; + } + + /** + * Checks if a migration's dependencies can even theoretically be satisfied. + * + * @param string $name The class name of the migration + * @return bool|string False if fulfillable, string of missing migration name if unfulfillable + */ + public function unfulfillable($name) + { + if (isset($this->migration_state[$name])) + { + return false; + } + + if (!class_exists($name)) + { + return $name; + } + + $migration = $this->get_migration($name); + $depends = $migration->depends_on(); + + foreach ($depends as $depend) + { + $unfulfillable = $this->unfulfillable($depend); + if ($unfulfillable !== false) + { + return $unfulfillable; + } + } + + return false; + } + + /** + * Checks whether all available, fulfillable migrations have been applied. + * + * @return bool Whether the migrations have been applied + */ + public function finished() + { + foreach ($this->migrations as $name) + { + if (!isset($this->migration_state[$name])) + { + // skip unfulfillable migrations, but fulfillables mean we + // are not finished yet + if ($this->unfulfillable($name) !== false) + { + continue; + } + return false; + } + + $migration = $this->migration_state[$name]; + + if (!$migration['migration_schema_done'] || !$migration['migration_data_done']) + { + return false; + } + } + + return true; + } + + /** + * Gets a migration state (whether it is installed and to what extent) + * + * @param string $migration String migration name to check if it is installed + * @return bool|array False if the migration has not at all been installed, array + */ + public function migration_state($migration) + { + if (!isset($this->migration_state[$migration])) + { + return false; + } + + return $this->migration_state[$migration]; + } + + /** + * Helper to get a migration + * + * @param string $name Name of the migration + * @return phpbb_db_migration + */ + protected function get_migration($name) + { + return new $name($this->config, $this->db, $this->db_tools, $this->phpbb_root_path, $this->php_ext, $this->table_prefix); + } + + /** + * This function adds all migrations sent to it to the migrations table + * + * THIS SHOULD NOT GENERALLY BE USED! THIS IS FOR THE PHPBB INSTALLER. + * THIS WILL THROW ERRORS IF MIGRATIONS ALREADY EXIST IN THE TABLE, DO NOT CALL MORE THAN ONCE! + * + * @param array $migrations Array of migrations (names) to add to the migrations table + * @return null + */ + public function populate_migrations($migrations) + { + foreach ($migrations as $name) + { + if ($this->migration_state($name) === false) + { + $state = array( + 'migration_depends_on' => $name::depends_on(), + 'migration_schema_done' => true, + 'migration_data_done' => true, + 'migration_data_state' => '', + 'migration_start_time' => time(), + 'migration_end_time' => time(), + ); + $this->set_migration_state($name, $state); + } + } + } + + /** + * Load migration data files from a directory + * + * @param phpbb_extension_finder $finder + * @param string $path Path to migration data files + * @param bool $check_fulfillable If TRUE (default), we will check + * if all of the migrations are fulfillable after loading them. + * If FALSE, we will not check. You SHOULD check at least once + * to prevent errors (if including multiple directories, check + * with the last call to prevent throwing errors unnecessarily). + * @return array Array of migration names + */ + public function load_migrations(phpbb_extension_finder $finder, $path, $check_fulfillable = true) + { + if (!is_dir($path)) + { + throw new phpbb_db_migration_exception('DIRECTORY INVALID', $path); + } + + $migrations = array(); + + $files = $finder + ->extension_directory("/") + ->find_from_paths(array('/' => $path)); + foreach ($files as $file) + { + $migrations[$file['path'] . $file['filename']] = ''; + } + $migrations = $finder->get_classes_from_files($migrations); + + foreach ($migrations as $migration) + { + if (!in_array($migration, $this->migrations)) + { + $this->migrations[] = $migration; + } + } + + if ($check_fulfillable) + { + foreach ($this->migrations as $name) + { + $unfulfillable = $this->unfulfillable($name); + if ($unfulfillable !== false) + { + throw new phpbb_db_migration_exception('MIGRATION_NOT_FULFILLABLE', $name, $unfulfillable); + } + } + } + + return $this->migrations; + } +} diff --git a/phpBB/phpbb/db/sql_insert_buffer.php b/phpBB/phpbb/db/sql_insert_buffer.php new file mode 100644 index 0000000000..c18f908429 --- /dev/null +++ b/phpBB/phpbb/db/sql_insert_buffer.php @@ -0,0 +1,150 @@ +sql_multi_insert() include: +* +* - Going over max packet size of the database connection is usually prevented +* because the data is submitted in batches. +* +* - Reaching database connection timeout is usually prevented because +* submission of batches talks to the database every now and then. +* +* - Usage of less PHP memory because data no longer needed is discarded on +* buffer flush. +* +* Attention: +* Please note that users of this class have to call flush() to flush the +* remaining rows to the database after their batch insert operation is +* finished. +* +* Usage: +* +* $buffer = new phpbb_db_sql_insert_buffer($db, 'test_table', 1234); +* +* while (do_stuff()) +* { +* $buffer->insert(array( +* 'column1' => 'value1', +* 'column2' => 'value2', +* )); +* } +* +* $buffer->flush(); +* +* +* @package dbal +*/ +class phpbb_db_sql_insert_buffer +{ + /** @var phpbb_db_driver */ + protected $db; + + /** @var string */ + protected $table_name; + + /** @var int */ + protected $max_buffered_rows; + + /** @var array */ + protected $buffer = array(); + + /** + * @param phpbb_db_driver $db + * @param string $table_name + * @param int $max_buffered_rows + */ + public function __construct(phpbb_db_driver $db, $table_name, $max_buffered_rows = 500) + { + $this->db = $db; + $this->table_name = $table_name; + $this->max_buffered_rows = $max_buffered_rows; + } + + /** + * Inserts a single row into the buffer if multi insert is supported by the + * database (otherwise an insert query is sent immediately). Then flushes + * the buffer if the number of rows in the buffer is now greater than or + * equal to $max_buffered_rows. + * + * @param array $row + * + * @return bool True when some data was flushed to the database. + * False otherwise. + */ + public function insert(array $row) + { + $this->buffer[] = $row; + + // Flush buffer if it is full or when DB does not support multi inserts. + // In the later case, the buffer will always only contain one row. + if (!$this->db->multi_insert || sizeof($this->buffer) >= $this->max_buffered_rows) + { + return $this->flush(); + } + + return false; + } + + /** + * Inserts a row set, i.e. an array of rows, by calling insert(). + * + * Please note that it is in most cases better to use insert() instead of + * first building a huge rowset. Or at least sizeof($rows) should be kept + * small. + * + * @param array $rows + * + * @return bool True when some data was flushed to the database. + * False otherwise. + */ + public function insert_all(array $rows) + { + // Using bitwise |= because PHP does not have logical ||= + $result = 0; + + foreach ($rows as $row) + { + $result |= (int) $this->insert($row); + } + + return (bool) $result; + } + + /** + * Flushes the buffer content to the DB and clears the buffer. + * + * @return bool True when some data was flushed to the database. + * False otherwise. + */ + public function flush() + { + if (!empty($this->buffer)) + { + $this->db->sql_multi_insert($this->table_name, $this->buffer); + $this->buffer = array(); + + return true; + } + + return false; + } +} diff --git a/phpBB/phpbb/di/extension/config.php b/phpBB/phpbb/di/extension/config.php new file mode 100644 index 0000000000..6c272a6588 --- /dev/null +++ b/phpBB/phpbb/di/extension/config.php @@ -0,0 +1,84 @@ +config_file = $config_file; + } + + /** + * Loads a specific configuration. + * + * @param array $config An array of configuration values + * @param ContainerBuilder $container A ContainerBuilder instance + * + * @throws InvalidArgumentException When provided tag is not defined in this extension + */ + public function load(array $config, ContainerBuilder $container) + { + require($this->config_file); + + $container->setParameter('core.adm_relative_path', (isset($phpbb_adm_relative_path) ? $phpbb_adm_relative_path : 'adm/')); + $container->setParameter('core.table_prefix', $table_prefix); + $container->setParameter('cache.driver.class', $this->convert_30_acm_type($acm_type)); + $container->setParameter('dbal.driver.class', phpbb_convert_30_dbms_to_31($dbms)); + $container->setParameter('dbal.dbhost', $dbhost); + $container->setParameter('dbal.dbuser', $dbuser); + $container->setParameter('dbal.dbpasswd', $dbpasswd); + $container->setParameter('dbal.dbname', $dbname); + $container->setParameter('dbal.dbport', $dbport); + $container->setParameter('dbal.new_link', defined('PHPBB_DB_NEW_LINK') && PHPBB_DB_NEW_LINK); + } + + /** + * Returns the recommended alias to use in XML. + * + * This alias is also the mandatory prefix to use when using YAML. + * + * @return string The alias + */ + public function getAlias() + { + return 'config'; + } + + /** + * Convert 3.0 ACM type to 3.1 cache driver class name + * + * @param string $acm_type ACM type + * @return cache driver class + */ + protected function convert_30_acm_type($acm_type) + { + if (preg_match('#^[a-z]+$#', $acm_type)) + { + return 'phpbb_cache_driver_'.$acm_type; + } + + return $acm_type; + } +} diff --git a/phpBB/phpbb/di/extension/core.php b/phpBB/phpbb/di/extension/core.php new file mode 100644 index 0000000000..9c36ba2fc4 --- /dev/null +++ b/phpBB/phpbb/di/extension/core.php @@ -0,0 +1,69 @@ +root_path = $root_path; + } + + /** + * Loads a specific configuration. + * + * @param array $config An array of configuration values + * @param ContainerBuilder $container A ContainerBuilder instance + * + * @throws InvalidArgumentException When provided tag is not defined in this extension + */ + public function load(array $config, ContainerBuilder $container) + { + $loader = new YamlFileLoader($container, new FileLocator(phpbb_realpath($this->root_path . 'config'))); + $loader->load('services.yml'); + } + + /** + * Returns the recommended alias to use in XML. + * + * This alias is also the mandatory prefix to use when using YAML. + * + * @return string The alias + */ + public function getAlias() + { + return 'core'; + } +} diff --git a/phpBB/phpbb/di/extension/ext.php b/phpBB/phpbb/di/extension/ext.php new file mode 100644 index 0000000000..7d9b433751 --- /dev/null +++ b/phpBB/phpbb/di/extension/ext.php @@ -0,0 +1,69 @@ + $path) + { + $this->paths[] = $path; + } + } + + /** + * Loads a specific configuration. + * + * @param array $config An array of configuration values + * @param ContainerBuilder $container A ContainerBuilder instance + * + * @throws InvalidArgumentException When provided tag is not defined in this extension + */ + public function load(array $config, ContainerBuilder $container) + { + foreach ($this->paths as $path) + { + if (file_exists($path . '/config/services.yml')) + { + $loader = new YamlFileLoader($container, new FileLocator(phpbb_realpath($path . '/config'))); + $loader->load('services.yml'); + } + } + } + + /** + * Returns the recommended alias to use in XML. + * + * This alias is also the mandatory prefix to use when using YAML. + * + * @return string The alias + */ + public function getAlias() + { + return 'ext'; + } +} diff --git a/phpBB/phpbb/di/pass/collection_pass.php b/phpBB/phpbb/di/pass/collection_pass.php new file mode 100644 index 0000000000..63a5c7dfc4 --- /dev/null +++ b/phpBB/phpbb/di/pass/collection_pass.php @@ -0,0 +1,46 @@ +findTaggedServiceIds('service_collection') as $id => $data) + { + $definition = $container->getDefinition($id); + + foreach ($container->findTaggedServiceIds($data[0]['tag']) as $service_id => $service_data) + { + $definition->addMethodCall('add', array($service_id)); + } + } + } +} diff --git a/phpBB/phpbb/di/pass/kernel_pass.php b/phpBB/phpbb/di/pass/kernel_pass.php new file mode 100644 index 0000000000..a701ebcfa6 --- /dev/null +++ b/phpBB/phpbb/di/pass/kernel_pass.php @@ -0,0 +1,68 @@ +getDefinition('dispatcher'); + + foreach ($container->findTaggedServiceIds('kernel.event_listener') as $id => $events) + { + foreach ($events as $event) + { + $priority = isset($event['priority']) ? $event['priority'] : 0; + + if (!isset($event['event'])) + { + throw new InvalidArgumentException(sprintf('Service "%1$s" must define the "event" attribute on "kernel.event_listener" tags.', $id)); + } + + if (!isset($event['method'])) + { + throw new InvalidArgumentException(sprintf('Service "%1$s" must define the "method" attribute on "kernel.event_listener" tags.', $id)); + } + + $definition->addMethodCall('addListenerService', array($event['event'], array($id, $event['method']), $priority)); + } + } + + foreach ($container->findTaggedServiceIds('kernel.event_subscriber') as $id => $attributes) + { + // We must assume that the class value has been correctly filled, even if the service is created by a factory + $class = $container->getDefinition($id)->getClass(); + + $refClass = new ReflectionClass($class); + $interface = 'Symfony\Component\EventDispatcher\EventSubscriberInterface'; + if (!$refClass->implementsInterface($interface)) + { + throw new InvalidArgumentException(sprintf('Service "%1$s" must implement interface "%2$s".', $id, $interface)); + } + + $definition->addMethodCall('addSubscriberService', array($id, $class)); + } + } +} diff --git a/phpBB/phpbb/di/service_collection.php b/phpBB/phpbb/di/service_collection.php new file mode 100644 index 0000000000..880cb46d4d --- /dev/null +++ b/phpBB/phpbb/di/service_collection.php @@ -0,0 +1,49 @@ +container = $container; + } + + /** + * Add a service to the collection + * + * @param string $name The service name + * @return null + */ + public function add($name) + { + $task = $this->container->get($name); + + $this->offsetSet($name, $task); + } +} diff --git a/phpBB/phpbb/error_collector.php b/phpBB/phpbb/error_collector.php new file mode 100644 index 0000000000..358da747b8 --- /dev/null +++ b/phpBB/phpbb/error_collector.php @@ -0,0 +1,62 @@ +errors = array(); + } + + function install() + { + set_error_handler(array(&$this, 'error_handler')); + } + + function uninstall() + { + restore_error_handler(); + } + + function error_handler($errno, $msg_text, $errfile, $errline) + { + $this->errors[] = array($errno, $msg_text, $errfile, $errline); + } + + function format_errors() + { + $text = ''; + foreach ($this->errors as $error) + { + if (!empty($text)) + { + $text .= "
\n"; + } + + list($errno, $msg_text, $errfile, $errline) = $error; + + // Prevent leakage of local path to phpBB install + $errfile = phpbb_filter_root_path($errfile); + + $text .= "Errno $errno: $msg_text at $errfile line $errline"; + } + + return $text; + } +} diff --git a/phpBB/phpbb/event/data.php b/phpBB/phpbb/event/data.php new file mode 100644 index 0000000000..70718ff0ae --- /dev/null +++ b/phpBB/phpbb/event/data.php @@ -0,0 +1,68 @@ +set_data($data); + } + + public function set_data(array $data = array()) + { + $this->data = $data; + } + + public function get_data() + { + return $this->data; + } + + /** + * Returns data filtered to only include specified keys. + * + * This effectively discards any keys added to data by hooks. + */ + public function get_data_filtered($keys) + { + return array_intersect_key($this->data, array_flip($keys)); + } + + public function offsetExists($offset) + { + return isset($this->data[$offset]); + } + + public function offsetGet($offset) + { + return isset($this->data[$offset]) ? $this->data[$offset] : null; + } + + public function offsetSet($offset, $value) + { + $this->data[$offset] = $value; + } + + public function offsetUnset($offset) + { + unset($this->data[$offset]); + } +} diff --git a/phpBB/phpbb/event/dispatcher.php b/phpBB/phpbb/event/dispatcher.php new file mode 100644 index 0000000000..4f637ce3bb --- /dev/null +++ b/phpBB/phpbb/event/dispatcher.php @@ -0,0 +1,42 @@ +trigger_event('core.index', compact($vars))); +* +*/ +class phpbb_event_dispatcher extends ContainerAwareEventDispatcher +{ + public function trigger_event($eventName, $data = array()) + { + $event = new phpbb_event_data($data); + $this->dispatch($eventName, $event); + return $event->get_data_filtered(array_keys($data)); + } +} diff --git a/phpBB/phpbb/event/extension_subscriber_loader.php b/phpBB/phpbb/event/extension_subscriber_loader.php new file mode 100644 index 0000000000..d933b943d7 --- /dev/null +++ b/phpBB/phpbb/event/extension_subscriber_loader.php @@ -0,0 +1,46 @@ +dispatcher = $dispatcher; + $this->extension_manager = $extension_manager; + } + + public function load() + { + $finder = $this->extension_manager->get_finder(); + $subscriber_classes = $finder + ->extension_directory('/event') + ->suffix('listener') + ->core_path('event/') + ->get_classes(); + + foreach ($subscriber_classes as $class) + { + $subscriber = new $class(); + $this->dispatcher->addSubscriber($subscriber); + } + } +} diff --git a/phpBB/phpbb/event/kernel_exception_subscriber.php b/phpBB/phpbb/event/kernel_exception_subscriber.php new file mode 100644 index 0000000000..f90989a74c --- /dev/null +++ b/phpBB/phpbb/event/kernel_exception_subscriber.php @@ -0,0 +1,85 @@ +template = $template; + $this->user = $user; + } + + /** + * This listener is run when the KernelEvents::EXCEPTION event is triggered + * + * @param GetResponseForExceptionEvent $event + * @return null + */ + public function on_kernel_exception(GetResponseForExceptionEvent $event) + { + page_header($this->user->lang('INFORMATION')); + + $exception = $event->getException(); + + $this->template->assign_vars(array( + 'MESSAGE_TITLE' => $this->user->lang('INFORMATION'), + 'MESSAGE_TEXT' => $exception->getMessage(), + )); + + $this->template->set_filenames(array( + 'body' => 'message_body.html', + )); + + page_footer(true, false, false); + + + $status_code = $exception instanceof HttpException ? $exception->getStatusCode() : 500; + $response = new Response($this->template->assign_display('body'), $status_code); + $event->setResponse($response); + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::EXCEPTION => 'on_kernel_exception', + ); + } +} diff --git a/phpBB/phpbb/event/kernel_request_subscriber.php b/phpBB/phpbb/event/kernel_request_subscriber.php new file mode 100644 index 0000000000..afb8464f80 --- /dev/null +++ b/phpBB/phpbb/event/kernel_request_subscriber.php @@ -0,0 +1,83 @@ +finder = $finder; + $this->root_path = $root_path; + $this->php_ext = $php_ext; + } + + /** + * This listener is run when the KernelEvents::REQUEST event is triggered + * + * This is responsible for setting up the routing information + * + * @param GetResponseEvent $event + * @return null + */ + public function on_kernel_request(GetResponseEvent $event) + { + $request = $event->getRequest(); + $context = new RequestContext(); + $context->fromRequest($request); + + $matcher = phpbb_get_url_matcher($this->finder, $context, $this->root_path, $this->php_ext); + $router_listener = new RouterListener($matcher, $context); + $router_listener->onKernelRequest($event); + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::REQUEST => 'on_kernel_request', + ); + } +} diff --git a/phpBB/phpbb/event/kernel_terminate_subscriber.php b/phpBB/phpbb/event/kernel_terminate_subscriber.php new file mode 100644 index 0000000000..1eaf890e42 --- /dev/null +++ b/phpBB/phpbb/event/kernel_terminate_subscriber.php @@ -0,0 +1,43 @@ + 'on_kernel_terminate', + ); + } +} diff --git a/phpBB/phpbb/extension/base.php b/phpBB/phpbb/extension/base.php new file mode 100644 index 0000000000..c4462b64d8 --- /dev/null +++ b/phpBB/phpbb/extension/base.php @@ -0,0 +1,135 @@ +container = $container; + $this->extension_finder = $extension_finder; + $this->migrator = $migrator; + + $this->extension_name = $extension_name; + $this->extension_path = $extension_path; + } + + /** + * Single enable step that installs any included migrations + * + * @param mixed $old_state State returned by previous call of this method + * @return false Indicates no further steps are required + */ + public function enable_step($old_state) + { + $migrations = $this->get_migration_file_list(); + + $this->migrator->set_migrations($migrations); + + $this->migrator->update(); + + return !$this->migrator->finished(); + } + + /** + * Single disable step that does nothing + * + * @param mixed $old_state State returned by previous call of this method + * @return false Indicates no further steps are required + */ + public function disable_step($old_state) + { + return false; + } + + /** + * Single purge step that reverts any included and installed migrations + * + * @param mixed $old_state State returned by previous call of this method + * @return false Indicates no further steps are required + */ + public function purge_step($old_state) + { + $migrations = $this->get_migration_file_list(); + + $this->migrator->set_migrations($migrations); + + foreach ($migrations as $migration) + { + while ($this->migrator->migration_state($migration) !== false) + { + $this->migrator->revert($migration); + + return true; + } + } + + return false; + } + + /** + * Get the list of migration files from this extension + * + * @return array + */ + protected function get_migration_file_list() + { + static $migrations = false; + + if ($migrations !== false) + { + return $migrations; + } + + // Only have the finder search in this extension path directory + $migrations = $this->extension_finder + ->extension_directory('/migrations') + ->find_from_extension($this->extension_name, $this->extension_path); + $migrations = $this->extension_finder->get_classes_from_files($migrations); + + return $migrations; + } +} diff --git a/phpBB/phpbb/extension/exception.php b/phpBB/phpbb/extension/exception.php new file mode 100644 index 0000000000..e08a8912ea --- /dev/null +++ b/phpBB/phpbb/extension/exception.php @@ -0,0 +1,27 @@ +getMessage(); + } +} \ No newline at end of file diff --git a/phpBB/phpbb/extension/finder.php b/phpBB/phpbb/extension/finder.php new file mode 100644 index 0000000000..155a41cda5 --- /dev/null +++ b/phpBB/phpbb/extension/finder.php @@ -0,0 +1,523 @@ +cached_queries in $this->cache. + * + * Allows the use of multiple differently configured finders with the same cache. + * @var string + */ + protected $cache_name; + + /** + * An associative array, containing all search parameters set in methods. + * @var array + */ + protected $query; + + /** + * A map from md5 hashes of serialized queries to their previously retrieved + * results. + * @var array + */ + protected $cached_queries; + + /** + * Creates a new finder instance with its dependencies + * + * @param phpbb_extension_manager $extension_manager An extension manager + * instance that provides the finder with a list of active + * extensions and their locations + * @param phpbb_filesystem $filesystem Filesystem instance + * @param string $phpbb_root_path Path to the phpbb root directory + * @param phpbb_cache_driver_interface $cache A cache instance or null + * @param string $php_ext php file extension + * @param string $cache_name The name of the cache variable, defaults to + * _ext_finder + */ + public function __construct(phpbb_extension_manager $extension_manager, phpbb_filesystem $filesystem, $phpbb_root_path = '', phpbb_cache_driver_interface $cache = null, $php_ext = 'php', $cache_name = '_ext_finder') + { + $this->extension_manager = $extension_manager; + $this->filesystem = $filesystem; + $this->phpbb_root_path = $phpbb_root_path; + $this->cache = $cache; + $this->php_ext = $php_ext; + $this->cache_name = $cache_name; + + $this->query = array( + 'core_path' => false, + 'core_suffix' => false, + 'core_prefix' => false, + 'core_directory' => false, + 'extension_suffix' => false, + 'extension_prefix' => false, + 'extension_directory' => false, + ); + + $this->cached_queries = ($this->cache) ? $this->cache->get($this->cache_name) : false; + } + + /** + * Sets a core path to be searched in addition to extensions + * + * @param string $core_path The path relative to phpbb_root_path + * @return phpbb_extension_finder This object for chaining calls + */ + public function core_path($core_path) + { + $this->query['core_path'] = $core_path; + return $this; + } + + /** + * Sets the suffix all files found in extensions and core must match. + * + * There is no default file extension, so to find PHP files only, you will + * have to specify .php as a suffix. However when using get_classes, the .php + * file extension is automatically added to suffixes. + * + * @param string $suffix A filename suffix + * @return phpbb_extension_finder This object for chaining calls + */ + public function suffix($suffix) + { + $this->core_suffix($suffix); + $this->extension_suffix($suffix); + return $this; + } + + /** + * Sets a suffix all files found in extensions must match + * + * There is no default file extension, so to find PHP files only, you will + * have to specify .php as a suffix. However when using get_classes, the .php + * file extension is automatically added to suffixes. + * + * @param string $extension_suffix A filename suffix + * @return phpbb_extension_finder This object for chaining calls + */ + public function extension_suffix($extension_suffix) + { + $this->query['extension_suffix'] = $extension_suffix; + return $this; + } + + /** + * Sets a suffix all files found in the core path must match + * + * There is no default file extension, so to find PHP files only, you will + * have to specify .php as a suffix. However when using get_classes, the .php + * file extension is automatically added to suffixes. + * + * @param string $core_suffix A filename suffix + * @return phpbb_extension_finder This object for chaining calls + */ + public function core_suffix($core_suffix) + { + $this->query['core_suffix'] = $core_suffix; + return $this; + } + + /** + * Sets the prefix all files found in extensions and core must match + * + * @param string $prefix A filename prefix + * @return phpbb_extension_finder This object for chaining calls + */ + public function prefix($prefix) + { + $this->core_prefix($prefix); + $this->extension_prefix($prefix); + return $this; + } + + /** + * Sets a prefix all files found in extensions must match + * + * @param string $extension_prefix A filename prefix + * @return phpbb_extension_finder This object for chaining calls + */ + public function extension_prefix($extension_prefix) + { + $this->query['extension_prefix'] = $extension_prefix; + return $this; + } + + /** + * Sets a prefix all files found in the core path must match + * + * @param string $core_prefix A filename prefix + * @return phpbb_extension_finder This object for chaining calls + */ + public function core_prefix($core_prefix) + { + $this->query['core_prefix'] = $core_prefix; + return $this; + } + + /** + * Sets a directory all files found in extensions and core must be contained in + * + * Automatically sets the core_directory if its value does not differ from + * the current directory. + * + * @param string $directory + * @return phpbb_extension_finder This object for chaining calls + */ + public function directory($directory) + { + $this->core_directory($directory); + $this->extension_directory($directory); + return $this; + } + + /** + * Sets a directory all files found in extensions must be contained in + * + * @param string $extension_directory + * @return phpbb_extension_finder This object for chaining calls + */ + public function extension_directory($extension_directory) + { + $this->query['extension_directory'] = $this->sanitise_directory($extension_directory); + return $this; + } + + /** + * Sets a directory all files found in the core path must be contained in + * + * @param string $core_directory + * @return phpbb_extension_finder This object for chaining calls + */ + public function core_directory($core_directory) + { + $this->query['core_directory'] = $this->sanitise_directory($core_directory); + return $this; + } + + /** + * Removes occurances of /./ and makes sure path ends without trailing slash + * + * @param string $directory A directory pattern + * @return string A cleaned up directory pattern + */ + protected function sanitise_directory($directory) + { + $directory = $this->filesystem->clean_path($directory); + $dir_len = strlen($directory); + + if ($dir_len > 1 && $directory[$dir_len - 1] === '/') + { + $directory = substr($directory, 0, -1); + } + + return $directory; + } + + /** + * Finds classes matching the configured options if they follow phpBB naming rules. + * + * The php file extension is automatically added to suffixes. + * + * Note: If a file is matched but contains a class name not following the + * phpBB naming rules an incorrect class name will be returned. + * + * @param bool $cache Whether the result should be cached + * @param bool $use_all_available Use all available instead of just all + * enabled extensions + * @return array An array of found class names + */ + public function get_classes($cache = true, $use_all_available = false) + { + $this->query['extension_suffix'] .= '.' . $this->php_ext; + $this->query['core_suffix'] .= '.' . $this->php_ext; + + $files = $this->find($cache, false, $use_all_available); + + return $this->get_classes_from_files($files); + } + + /** + * Get class names from a list of files + * + * @param array $files Array of files (from find()) + * @return array Array of class names + */ + public function get_classes_from_files($files) + { + $classes = array(); + foreach ($files as $file => $ext_name) + { + $file = preg_replace('#^(phpbb|includes)/#', '', $file); + + $classes[] = 'phpbb_' . str_replace('/', '_', substr($file, 0, -strlen('.' . $this->php_ext))); + } + return $classes; + } + + /** + * Finds all directories matching the configured options + * + * @param bool $cache Whether the result should be cached + * @param bool $use_all_available Use all available instead of just all + * enabled extensions + * @param bool $extension_keys Whether the result should have extension name as array key + * @return array An array of paths to found directories + */ + public function get_directories($cache = true, $use_all_available = false, $extension_keys = false) + { + return $this->find_with_root_path($cache, true, $use_all_available, $extension_keys); + } + + /** + * Finds all files matching the configured options. + * + * @param bool $cache Whether the result should be cached + * @param bool $use_all_available Use all available instead of just all + * enabled extensions + * @return array An array of paths to found files + */ + public function get_files($cache = true, $use_all_available = false) + { + return $this->find_with_root_path($cache, false, $use_all_available); + } + + /** + * A wrapper around the general find which prepends a root path to results + * + * @param bool $cache Whether the result should be cached + * @param bool $is_dir Directories will be returned when true, only files + * otherwise + * @param bool $use_all_available Use all available instead of just all + * enabled extensions + * @param bool $extension_keys If true, result will be associative array + * with extension name as key + * @return array An array of paths to found items + */ + protected function find_with_root_path($cache = true, $is_dir = false, $use_all_available = false, $extension_keys = false) + { + $items = $this->find($cache, $is_dir, $use_all_available); + + $result = array(); + foreach ($items as $item => $ext_name) + { + if ($extension_keys) + { + $result[$ext_name] = $this->phpbb_root_path . $item; + } + else + { + $result[] = $this->phpbb_root_path . $item; + } + } + + return $result; + } + + /** + * Finds all file system entries matching the configured options + * + * @param bool $cache Whether the result should be cached + * @param bool $is_dir Directories will be returned when true, only files + * otherwise + * @param bool $use_all_available Use all available instead of just all + * enabled extensions + * @return array An array of paths to found items + */ + public function find($cache = true, $is_dir = false, $use_all_available = false) + { + if ($use_all_available) + { + $extensions = $this->extension_manager->all_available(); + } + else + { + $extensions = $this->extension_manager->all_enabled(); + } + + if ($this->query['core_path']) + { + $extensions['/'] = $this->phpbb_root_path . $this->query['core_path']; + } + + $files = array(); + $file_list = $this->find_from_paths($extensions, $cache, $is_dir); + + foreach ($file_list as $file) + { + $files[$file['named_path']] = $file['ext_name']; + } + + return $files; + } + + /** + * Finds all file system entries matching the configured options for one + * specific extension + * + * @param string $extension_name Name of the extension + * @param string $extension_path Relative path to the extension root directory + * @param bool $cache Whether the result should be cached + * @param bool $is_dir Directories will be returned when true, only files + * otherwise + * @return array An array of paths to found items + */ + public function find_from_extension($extension_name, $extension_path, $cache = true, $is_dir = false) + { + $extensions = array( + $extension_name => $extension_path, + ); + + $files = array(); + $file_list = $this->find_from_paths($extensions, $cache, $is_dir); + + foreach ($file_list as $file) + { + $files[$file['named_path']] = $file['ext_name']; + } + + return $files; + } + + /** + * Finds all file system entries matching the configured options from + * an array of paths + * + * @param array $extensions Array of extensions (name => full relative path) + * @param bool $cache Whether the result should be cached + * @param bool $is_dir Directories will be returned when true, only files + * otherwise + * @return array An array of paths to found items + */ + public function find_from_paths($extensions, $cache = true, $is_dir = false) + { + $this->query['is_dir'] = $is_dir; + $query = md5(serialize($this->query) . serialize($extensions)); + + if (!defined('DEBUG') && $cache && isset($this->cached_queries[$query])) + { + return $this->cached_queries[$query]; + } + + $files = array(); + + foreach ($extensions as $name => $path) + { + $ext_name = $name; + + if (!file_exists($path)) + { + continue; + } + + if ($name === '/') + { + $location = $this->query['core_path']; + $name = ''; + $suffix = $this->query['core_suffix']; + $prefix = $this->query['core_prefix']; + $directory = $this->query['core_directory']; + } + else + { + $location = 'ext/'; + $name .= '/'; + $suffix = $this->query['extension_suffix']; + $prefix = $this->query['extension_prefix']; + $directory = $this->query['extension_directory']; + } + + // match only first directory if leading slash is given + if ($directory === '/') + { + $directory_pattern = '^' . preg_quote(DIRECTORY_SEPARATOR, '#'); + } + else if ($directory && $directory[0] === '/') + { + $directory_pattern = '^' . preg_quote(str_replace('/', DIRECTORY_SEPARATOR, $directory) . DIRECTORY_SEPARATOR, '#'); + } + else + { + $directory_pattern = preg_quote(DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $directory) . DIRECTORY_SEPARATOR, '#'); + } + if ($is_dir) + { + $directory_pattern .= '$'; + } + $directory_pattern = '#' . $directory_pattern . '#'; + + $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path), RecursiveIteratorIterator::SELF_FIRST); + foreach ($iterator as $file_info) + { + $filename = $file_info->getFilename(); + if ($filename == '.' || $filename == '..') + { + continue; + } + + if ($file_info->isDir() == $is_dir) + { + if ($is_dir) + { + $relative_path = $iterator->getInnerIterator()->getSubPath() . DIRECTORY_SEPARATOR . basename($filename) . DIRECTORY_SEPARATOR; + if ($relative_path[0] !== DIRECTORY_SEPARATOR) + { + $relative_path = DIRECTORY_SEPARATOR . $relative_path; + } + } + else + { + $relative_path = DIRECTORY_SEPARATOR . $iterator->getInnerIterator()->getSubPathname(); + } + + if ((!$suffix || substr($relative_path, -strlen($suffix)) === $suffix) && + (!$prefix || substr($filename, 0, strlen($prefix)) === $prefix) && + (!$directory || preg_match($directory_pattern, $relative_path))) + { + $files[] = array( + 'named_path' => str_replace(DIRECTORY_SEPARATOR, '/', $location . $name . substr($relative_path, 1)), + 'ext_name' => $ext_name, + 'path' => str_replace(array(DIRECTORY_SEPARATOR, $this->phpbb_root_path), array('/', ''), $file_info->getPath()) . '/', + 'filename' => $filename, + ); + } + } + } + } + + if ($cache && $this->cache) + { + $this->cached_queries[$query] = $files; + $this->cache->put($this->cache_name, $this->cached_queries); + } + + return $files; + } +} diff --git a/phpBB/phpbb/extension/interface.php b/phpBB/phpbb/extension/interface.php new file mode 100644 index 0000000000..7b36a12bf6 --- /dev/null +++ b/phpBB/phpbb/extension/interface.php @@ -0,0 +1,67 @@ +container = $container; + $this->phpbb_root_path = $phpbb_root_path; + $this->db = $db; + $this->config = $config; + $this->cache = $cache; + $this->filesystem = $filesystem; + $this->php_ext = $php_ext; + $this->extension_table = $extension_table; + $this->cache_name = $cache_name; + + $this->extensions = ($this->cache) ? $this->cache->get($this->cache_name) : false; + + if ($this->extensions === false) + { + $this->load_extensions(); + } + } + + /** + * Loads all extension information from the database + * + * @return null + */ + public function load_extensions() + { + $this->extensions = array(); + + // Do not try to load any extensions when installing or updating + // Note: database updater invokes this code, and in 3.0 + // there is no extension table therefore the rest of this function + // fails + if (defined('IN_INSTALL')) + { + return; + } + + $sql = 'SELECT * + FROM ' . $this->extension_table; + + $result = $this->db->sql_query($sql); + $extensions = $this->db->sql_fetchrowset($result); + $this->db->sql_freeresult($result); + + foreach ($extensions as $extension) + { + $extension['ext_path'] = $this->get_extension_path($extension['ext_name']); + $this->extensions[$extension['ext_name']] = $extension; + } + + ksort($this->extensions); + + if ($this->cache) + { + $this->cache->put($this->cache_name, $this->extensions); + } + } + + /** + * Generates the path to an extension + * + * @param string $name The name of the extension + * @param bool $phpbb_relative Whether the path should be relative to phpbb root + * @return string Path to an extension + */ + public function get_extension_path($name, $phpbb_relative = false) + { + $name = str_replace('.', '', $name); + + return (($phpbb_relative) ? $this->phpbb_root_path : '') . 'ext/' . $name . '/'; + } + + /** + * Instantiates the extension meta class for the extension with the given name + * + * @param string $name The extension name + * @return phpbb_extension_interface Instance of the extension meta class or + * phpbb_extension_base if the class does not exist + */ + public function get_extension($name) + { + $extension_class_name = 'phpbb_ext_' . str_replace('/', '_', $name) . '_ext'; + + $migrator = $this->container->get('migrator'); + + if (class_exists($extension_class_name)) + { + return new $extension_class_name($this->container, $this->get_finder(), $migrator, $name, $this->get_extension_path($name, true)); + } + else + { + return new phpbb_extension_base($this->container, $this->get_finder(), $migrator, $name, $this->get_extension_path($name, true)); + } + } + + /** + * Instantiates the metadata manager for the extension with the given name + * + * @param string $name The extension name + * @param string $template The template manager + * @return phpbb_extension_metadata_manager Instance of the metadata manager + */ + public function create_extension_metadata_manager($name, phpbb_template $template) + { + return new phpbb_extension_metadata_manager($name, $this->config, $this, $template, $this->phpbb_root_path); + } + + /** + * Runs a step of the extension enabling process. + * + * Allows the exentension to enable in a long running script that works + * in multiple steps across requests. State is kept for the extension + * in the extensions table. + * + * @param string $name The extension's name + * @return bool False if enabling is finished, true otherwise + */ + public function enable_step($name) + { + // ignore extensions that are already enabled + if (isset($this->extensions[$name]) && $this->extensions[$name]['ext_active']) + { + return false; + } + + $old_state = (isset($this->extensions[$name]['ext_state'])) ? unserialize($this->extensions[$name]['ext_state']) : false; + + $extension = $this->get_extension($name); + $state = $extension->enable_step($old_state); + + $active = ($state === false); + + $extension_data = array( + 'ext_name' => $name, + 'ext_active' => $active, + 'ext_state' => serialize($state), + ); + + $this->extensions[$name] = $extension_data; + $this->extensions[$name]['ext_path'] = $this->get_extension_path($extension_data['ext_name']); + ksort($this->extensions); + + $sql = 'SELECT COUNT(ext_name) as row_count + FROM ' . $this->extension_table . " + WHERE ext_name = '" . $this->db->sql_escape($name) . "'"; + $result = $this->db->sql_query($sql); + $count = $this->db->sql_fetchfield('row_count'); + $this->db->sql_freeresult($result); + + if ($count) + { + $sql = 'UPDATE ' . $this->extension_table . ' + SET ' . $this->db->sql_build_array('UPDATE', $extension_data) . " + WHERE ext_name = '" . $this->db->sql_escape($name) . "'"; + $this->db->sql_query($sql); + } + else + { + $sql = 'INSERT INTO ' . $this->extension_table . ' + ' . $this->db->sql_build_array('INSERT', $extension_data); + $this->db->sql_query($sql); + } + + if ($this->cache) + { + $this->cache->purge(); + } + + return !$active; + } + + /** + * Enables an extension + * + * This method completely enables an extension. But it could be long running + * so never call this in a script that has a max_execution time. + * + * @param string $name The extension's name + * @return null + */ + public function enable($name) + { + while ($this->enable_step($name)); + } + + /** + * Disables an extension + * + * Calls the disable method on the extension's meta class to allow it to + * process the event. + * + * @param string $name The extension's name + * @return bool False if disabling is finished, true otherwise + */ + public function disable_step($name) + { + // ignore extensions that are already disabled + if (!isset($this->extensions[$name]) || !$this->extensions[$name]['ext_active']) + { + return false; + } + + $old_state = unserialize($this->extensions[$name]['ext_state']); + + $extension = $this->get_extension($name); + $state = $extension->disable_step($old_state); + + // continue until the state is false + if ($state !== false) + { + $extension_data = array( + 'ext_state' => serialize($state), + ); + $this->extensions[$name]['ext_state'] = serialize($state); + + $sql = 'UPDATE ' . $this->extension_table . ' + SET ' . $this->db->sql_build_array('UPDATE', $extension_data) . " + WHERE ext_name = '" . $this->db->sql_escape($name) . "'"; + $this->db->sql_query($sql); + + if ($this->cache) + { + $this->cache->purge(); + } + + return true; + } + + $extension_data = array( + 'ext_active' => false, + 'ext_state' => serialize(false), + ); + $this->extensions[$name]['ext_active'] = false; + $this->extensions[$name]['ext_state'] = serialize(false); + + $sql = 'UPDATE ' . $this->extension_table . ' + SET ' . $this->db->sql_build_array('UPDATE', $extension_data) . " + WHERE ext_name = '" . $this->db->sql_escape($name) . "'"; + $this->db->sql_query($sql); + + if ($this->cache) + { + $this->cache->purge(); + } + + return false; + } + + /** + * Disables an extension + * + * Disables an extension completely at once. This process could run for a + * while so never call this in a script that has a max_execution time. + * + * @param string $name The extension's name + * @return null + */ + public function disable($name) + { + while ($this->disable_step($name)); + } + + /** + * Purge an extension + * + * Disables the extension first if active, and then calls purge on the + * extension's meta class to delete the extension's database content. + * + * @param string $name The extension's name + * @return bool False if purging is finished, true otherwise + */ + public function purge_step($name) + { + // ignore extensions that do not exist + if (!isset($this->extensions[$name])) + { + return false; + } + + // disable first if necessary + if ($this->extensions[$name]['ext_active']) + { + $this->disable($name); + } + + $old_state = unserialize($this->extensions[$name]['ext_state']); + + $extension = $this->get_extension($name); + $state = $extension->purge_step($old_state); + + // continue until the state is false + if ($state !== false) + { + $extension_data = array( + 'ext_state' => serialize($state), + ); + $this->extensions[$name]['ext_state'] = serialize($state); + + $sql = 'UPDATE ' . $this->extension_table . ' + SET ' . $this->db->sql_build_array('UPDATE', $extension_data) . " + WHERE ext_name = '" . $this->db->sql_escape($name) . "'"; + $this->db->sql_query($sql); + + if ($this->cache) + { + $this->cache->purge(); + } + + return true; + } + + unset($this->extensions[$name]); + + $sql = 'DELETE FROM ' . $this->extension_table . " + WHERE ext_name = '" . $this->db->sql_escape($name) . "'"; + $this->db->sql_query($sql); + + if ($this->cache) + { + $this->cache->purge(); + } + + return false; + } + + /** + * Purge an extension + * + * Purges an extension completely at once. This process could run for a while + * so never call this in a script that has a max_execution time. + * + * @param string $name The extension's name + * @return null + */ + public function purge($name) + { + while ($this->purge_step($name)); + } + + /** + * Retrieves a list of all available extensions on the filesystem + * + * @return array An array with extension names as keys and paths to the + * extension as values + */ + public function all_available() + { + $available = array(); + if (!is_dir($this->phpbb_root_path . 'ext/')) + { + return $available; + } + + $iterator = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($this->phpbb_root_path . 'ext/', FilesystemIterator::NEW_CURRENT_AND_KEY | FilesystemIterator::FOLLOW_SYMLINKS), + RecursiveIteratorIterator::SELF_FIRST); + foreach ($iterator as $file_info) + { + if ($file_info->isFile() && $file_info->getFilename() == 'ext.' . $this->php_ext) + { + $ext_name = $iterator->getInnerIterator()->getSubPath(); + + $ext_name = str_replace(DIRECTORY_SEPARATOR, '/', $ext_name); + + $available[$ext_name] = $this->phpbb_root_path . 'ext/' . $ext_name . '/'; + } + } + ksort($available); + return $available; + } + + /** + * Retrieves all configured extensions. + * + * All enabled and disabled extensions are considered configured. A purged + * extension that is no longer in the database is not configured. + * + * @return array An array with extension names as keys and and the + * database stored extension information as values + */ + public function all_configured() + { + $configured = array(); + foreach ($this->extensions as $name => $data) + { + $data['ext_path'] = $this->phpbb_root_path . $data['ext_path']; + $configured[$name] = $data; + } + return $configured; + } + + /** + * Retrieves all enabled extensions. + * + * @return array An array with extension names as keys and and the + * database stored extension information as values + */ + public function all_enabled() + { + $enabled = array(); + foreach ($this->extensions as $name => $data) + { + if ($data['ext_active']) + { + $enabled[$name] = $this->phpbb_root_path . $data['ext_path']; + } + } + return $enabled; + } + + /** + * Retrieves all disabled extensions. + * + * @return array An array with extension names as keys and and the + * database stored extension information as values + */ + public function all_disabled() + { + $disabled = array(); + foreach ($this->extensions as $name => $data) + { + if (!$data['ext_active']) + { + $disabled[$name] = $this->phpbb_root_path . $data['ext_path']; + } + } + return $disabled; + } + + /** + * Check to see if a given extension is available on the filesystem + * + * @param string $name Extension name to check NOTE: Can be user input + * @return bool Depending on whether or not the extension is available + */ + public function available($name) + { + return file_exists($this->get_extension_path($name, true)); + } + + /** + * Check to see if a given extension is enabled + * + * @param string $name Extension name to check + * @return bool Depending on whether or not the extension is enabled + */ + public function enabled($name) + { + return isset($this->extensions[$name]) && $this->extensions[$name]['ext_active']; + } + + /** + * Instantiates a phpbb_extension_finder. + * + * @return phpbb_extension_finder An extension finder instance + */ + public function get_finder() + { + return new phpbb_extension_finder($this, $this->filesystem, $this->phpbb_root_path, $this->cache, $this->php_ext, $this->cache_name . '_finder'); + } +} diff --git a/phpBB/phpbb/extension/metadata_manager.php b/phpBB/phpbb/extension/metadata_manager.php new file mode 100644 index 0000000000..14b77c085b --- /dev/null +++ b/phpBB/phpbb/extension/metadata_manager.php @@ -0,0 +1,371 @@ +config = $config; + $this->extension_manager = $extension_manager; + $this->template = $template; + $this->phpbb_root_path = $phpbb_root_path; + + $this->ext_name = $ext_name; + $this->metadata = array(); + $this->metadata_file = ''; + } + + /** + * Processes and gets the metadata requested + * + * @param string $element All for all metadata that it has and is valid, otherwise specify which section you want by its shorthand term. + * @return array Contains all of the requested metadata, throws an exception on failure + */ + public function get_metadata($element = 'all') + { + $this->set_metadata_file(); + + // Fetch the metadata + $this->fetch_metadata(); + + // Clean the metadata + $this->clean_metadata_array(); + + switch ($element) + { + case 'all': + default: + // Validate the metadata + if (!$this->validate()) + { + return false; + } + + return $this->metadata; + break; + + case 'name': + return ($this->validate('name')) ? $this->metadata['name'] : false; + break; + + case 'display-name': + if (isset($this->metadata['extra']['display-name'])) + { + return $this->metadata['extra']['display-name']; + } + else + { + return ($this->validate('name')) ? $this->metadata['name'] : false; + } + break; + } + } + + /** + * Sets the filepath of the metadata file + * + * @return boolean Set to true if it exists, throws an exception on failure + */ + private function set_metadata_file() + { + $ext_filepath = $this->extension_manager->get_extension_path($this->ext_name); + $metadata_filepath = $this->phpbb_root_path . $ext_filepath . 'composer.json'; + + $this->metadata_file = $metadata_filepath; + + if (!file_exists($this->metadata_file)) + { + throw new phpbb_extension_exception('The required file does not exist: ' . $this->metadata_file); + } + } + + /** + * Gets the contents of the composer.json file + * + * @return bool True if success, throws an exception on failure + */ + private function fetch_metadata() + { + if (!file_exists($this->metadata_file)) + { + throw new phpbb_extension_exception('The required file does not exist: ' . $this->metadata_file); + } + else + { + if (!($file_contents = file_get_contents($this->metadata_file))) + { + throw new phpbb_extension_exception('file_get_contents failed on ' . $this->metadata_file); + } + + if (($metadata = json_decode($file_contents, true)) === NULL) + { + throw new phpbb_extension_exception('json_decode failed on ' . $this->metadata_file); + } + + $this->metadata = $metadata; + + return true; + } + } + + /** + * This array handles the cleaning of the array + * + * @return array Contains the cleaned metadata array + */ + private function clean_metadata_array() + { + return $this->metadata; + } + + /** + * Validate fields + * + * @param string $name ("all" for display and enable validation + * "display" for name, type, and authors + * "name", "type") + * @return Bool True if valid, throws an exception if invalid + */ + public function validate($name = 'display') + { + // Basic fields + $fields = array( + 'name' => '#^[a-zA-Z0-9_\x7f-\xff]{2,}/[a-zA-Z0-9_\x7f-\xff]{2,}$#', + 'type' => '#^phpbb3-extension$#', + 'licence' => '#.+#', + 'version' => '#.+#', + ); + + switch ($name) + { + case 'all': + $this->validate('display'); + + $this->validate_enable(); + break; + + case 'display': + foreach ($fields as $field => $data) + { + $this->validate($field); + } + + $this->validate_authors(); + break; + + default: + if (isset($fields[$name])) + { + if (!isset($this->metadata[$name])) + { + throw new phpbb_extension_exception("Required meta field '$name' has not been set."); + } + + if (!preg_match($fields[$name], $this->metadata[$name])) + { + throw new phpbb_extension_exception("Meta field '$name' is invalid."); + } + } + break; + } + + return true; + } + + /** + * Validates the contents of the authors field + * + * @return boolean True when passes validation, throws exception if invalid + */ + public function validate_authors() + { + if (empty($this->metadata['authors'])) + { + throw new phpbb_extension_exception("Required meta field 'authors' has not been set."); + } + + foreach ($this->metadata['authors'] as $author) + { + if (!isset($author['name'])) + { + throw new phpbb_extension_exception("Required meta field 'author name' has not been set."); + } + } + + return true; + } + + /** + * This array handles the verification that this extension can be enabled on this board + * + * @return bool True if validation succeeded, False if failed + */ + public function validate_enable() + { + // Check for phpBB, PHP versions + if (!$this->validate_require_phpbb() || !$this->validate_require_php()) + { + return false; + } + + return true; + } + + + /** + * Validates the contents of the phpbb requirement field + * + * @return boolean True when passes validation + */ + public function validate_require_phpbb() + { + if (!isset($this->metadata['require']['phpbb'])) + { + return true; + } + + return $this->_validate_version($this->metadata['require']['phpbb'], $this->config['version']); + } + + /** + * Validates the contents of the php requirement field + * + * @return boolean True when passes validation + */ + public function validate_require_php() + { + if (!isset($this->metadata['require']['php'])) + { + return true; + } + + return $this->_validate_version($this->metadata['require']['php'], phpversion()); + } + + /** + * Version validation helper + * + * @param string $string The string for comparing to a version + * @param string $current_version The version to compare to + * @return bool True/False if meets version requirements + */ + private function _validate_version($string, $current_version) + { + // Allow them to specify their own comparison operator (ex: <3.1.2, >=3.1.0) + $comparison_matches = false; + preg_match('#[=<>]+#', $string, $comparison_matches); + + if (!empty($comparison_matches)) + { + return version_compare($current_version, str_replace(array($comparison_matches[0], ' '), '', $string), $comparison_matches[0]); + } + + return version_compare($current_version, $string, '>='); + } + + /** + * Outputs the metadata into the template + * + * @return null + */ + public function output_template_data() + { + $this->template->assign_vars(array( + 'META_NAME' => htmlspecialchars($this->metadata['name']), + 'META_TYPE' => htmlspecialchars($this->metadata['type']), + 'META_DESCRIPTION' => (isset($this->metadata['description'])) ? htmlspecialchars($this->metadata['description']) : '', + 'META_HOMEPAGE' => (isset($this->metadata['homepage'])) ? $this->metadata['homepage'] : '', + 'META_VERSION' => (isset($this->metadata['version'])) ? htmlspecialchars($this->metadata['version']) : '', + 'META_TIME' => (isset($this->metadata['time'])) ? htmlspecialchars($this->metadata['time']) : '', + 'META_LICENCE' => htmlspecialchars($this->metadata['licence']), + + 'META_REQUIRE_PHP' => (isset($this->metadata['require']['php'])) ? htmlspecialchars($this->metadata['require']['php']) : '', + 'META_REQUIRE_PHP_FAIL' => !$this->validate_require_php(), + + 'META_REQUIRE_PHPBB' => (isset($this->metadata['require']['phpbb'])) ? htmlspecialchars($this->metadata['require']['phpbb']) : '', + 'META_REQUIRE_PHPBB_FAIL' => !$this->validate_require_phpbb(), + + 'META_DISPLAY_NAME' => (isset($this->metadata['extra']['display-name'])) ? htmlspecialchars($this->metadata['extra']['display-name']) : '', + )); + + foreach ($this->metadata['authors'] as $author) + { + $this->template->assign_block_vars('meta_authors', array( + 'AUTHOR_NAME' => htmlspecialchars($author['name']), + 'AUTHOR_EMAIL' => (isset($author['email'])) ? $author['email'] : '', + 'AUTHOR_HOMEPAGE' => (isset($author['homepage'])) ? $author['homepage'] : '', + 'AUTHOR_ROLE' => (isset($author['role'])) ? htmlspecialchars($author['role']) : '', + )); + } + } +} diff --git a/phpBB/phpbb/extension/provider.php b/phpBB/phpbb/extension/provider.php new file mode 100644 index 0000000000..45b55e5cab --- /dev/null +++ b/phpBB/phpbb/extension/provider.php @@ -0,0 +1,76 @@ +extension_manager = $extension_manager; + } + + /** + * Finds items using the extension manager. + * + * @return array List of task names + */ + abstract protected function find(); + + /** + * Retrieve an iterator over all items + * + * @return ArrayIterator An iterator for the array of template paths + */ + public function getIterator() + { + if ($this->items === null) + { + $this->items = $this->find(); + } + + return new ArrayIterator($this->items); + } +} diff --git a/phpBB/phpbb/feed/base.php b/phpBB/phpbb/feed/base.php new file mode 100644 index 0000000000..296d830932 --- /dev/null +++ b/phpBB/phpbb/feed/base.php @@ -0,0 +1,261 @@ +config = $config; + $this->helper = $helper; + $this->db = $db; + $this->cache = $cache; + $this->user = $user; + $this->auth = $auth; + $this->content_visibility = $content_visibility; + $this->phpEx = $phpEx; + + $this->set_keys(); + + // Allow num_items to be string + if (is_string($this->num_items)) + { + $this->num_items = (int) $this->config[$this->num_items]; + + // A precaution + if (!$this->num_items) + { + $this->num_items = 10; + } + } + } + + /** + * Set keys. + */ + function set_keys() + { + } + + /** + * Open feed + */ + function open() + { + } + + /** + * Close feed + */ + function close() + { + if (!empty($this->result)) + { + $this->db->sql_freeresult($this->result); + } + } + + /** + * Set key + */ + function set($key, $value) + { + $this->keys[$key] = $value; + } + + /** + * Get key + */ + function get($key) + { + return (isset($this->keys[$key])) ? $this->keys[$key] : NULL; + } + + function get_readable_forums() + { + static $forum_ids; + + if (!isset($forum_ids)) + { + $forum_ids = array_keys($this->auth->acl_getf('f_read', true)); + } + + return $forum_ids; + } + + function get_moderator_approve_forums() + { + static $forum_ids; + + if (!isset($forum_ids)) + { + $forum_ids = array_keys($this->auth->acl_getf('m_approve', true)); + } + + return $forum_ids; + } + + function is_moderator_approve_forum($forum_id) + { + static $forum_ids; + + if (!isset($forum_ids)) + { + $forum_ids = array_flip($this->get_moderator_approve_forums()); + } + + return (isset($forum_ids[$forum_id])) ? true : false; + } + + function get_excluded_forums() + { + static $forum_ids; + + // Matches acp/acp_board.php + $cache_name = 'feed_excluded_forum_ids'; + + if (!isset($forum_ids) && ($forum_ids = $this->cache->get('_' . $cache_name)) === false) + { + $sql = 'SELECT forum_id + FROM ' . FORUMS_TABLE . ' + WHERE ' . $this->db->sql_bit_and('forum_options', FORUM_OPTION_FEED_EXCLUDE, '<> 0'); + $result = $this->db->sql_query($sql); + + $forum_ids = array(); + while ($forum_id = (int) $this->db->sql_fetchfield('forum_id')) + { + $forum_ids[$forum_id] = $forum_id; + } + $this->db->sql_freeresult($result); + + $this->cache->put('_' . $cache_name, $forum_ids); + } + + return $forum_ids; + } + + function is_excluded_forum($forum_id) + { + $forum_ids = $this->get_excluded_forums(); + + return isset($forum_ids[$forum_id]) ? true : false; + } + + function get_passworded_forums() + { + return $this->user->get_passworded_forums(); + } + + function get_item() + { + static $result; + + if (!isset($result)) + { + if (!$this->get_sql()) + { + return false; + } + + // Query database + $sql = $this->db->sql_build_query('SELECT', $this->sql); + $result = $this->db->sql_query_limit($sql, $this->num_items); + } + + return $this->db->sql_fetchrow($result); + } + + function user_viewprofile($row) + { + $author_id = (int) $row[$this->get('author_id')]; + + if ($author_id == ANONYMOUS) + { + // Since we cannot link to a profile, we just return GUEST + // instead of $row['username'] + return $this->user->lang['GUEST']; + } + + return '' . $row[$this->get('creator')] . ''; + } +} diff --git a/phpBB/phpbb/feed/factory.php b/phpBB/phpbb/feed/factory.php new file mode 100644 index 0000000000..63a1eb8ef0 --- /dev/null +++ b/phpBB/phpbb/feed/factory.php @@ -0,0 +1,129 @@ +container = $container; + $this->config = $config; + $this->db = $db; + } + + /** + * Return correct object for specified mode + * + * @param string $mode The feeds mode. + * @param int $forum_id Forum id specified by the script if forum feed provided. + * @param int $topic_id Topic id specified by the script if topic feed provided. + * + * @return object Returns correct feeds object for specified mode. + */ + function get_feed($mode, $forum_id, $topic_id) + { + switch ($mode) + { + case 'forums': + if (!$this->config['feed_overall_forums']) + { + return false; + } + + return $this->container->get('feed.forums'); + break; + + case 'topics': + case 'topics_new': + if (!$this->config['feed_topics_new']) + { + return false; + } + + return $this->container->get('feed.topics'); + break; + + case 'topics_active': + if (!$this->config['feed_topics_active']) + { + return false; + } + + return $this->container->get('feed.topics_active'); + break; + + case 'news': + // Get at least one news forum + $sql = 'SELECT forum_id + FROM ' . FORUMS_TABLE . ' + WHERE ' . $this->db->sql_bit_and('forum_options', FORUM_OPTION_FEED_NEWS, '<> 0'); + $result = $this->db->sql_query_limit($sql, 1, 0, 600); + $s_feed_news = (int) $this->db->sql_fetchfield('forum_id'); + $this->db->sql_freeresult($result); + + if (!$s_feed_news) + { + return false; + } + + return $this->container->get('feed.news'); + break; + + default: + if ($topic_id && $this->config['feed_topic']) + { + return $this->container->get('feed.topic') + ->set_topic_id($topic_id); + } + else if ($forum_id && $this->config['feed_forum']) + { + return $this->container->get('feed.forum') + ->set_forum_id($forum_id); + } + else if ($this->config['feed_overall']) + { + return $this->container->get('feed.overall'); + } + + return false; + break; + } + } +} diff --git a/phpBB/phpbb/feed/forum.php b/phpBB/phpbb/feed/forum.php new file mode 100644 index 0000000000..b5f0dd0f8f --- /dev/null +++ b/phpBB/phpbb/feed/forum.php @@ -0,0 +1,145 @@ +num_items} posts made +* within a specific forum. +* +* @package phpBB3 +*/ +class phpbb_feed_forum extends phpbb_feed_post_base +{ + var $forum_id = 0; + var $forum_data = array(); + + /** + * Set the Forum ID + * + * @param int $forum_id Forum ID + * @return phpbb_feed_forum + */ + public function set_forum_id($topic_id) + { + $this->forum_id = (int) $forum_id; + + return $this; + } + + function open() + { + // Check if forum exists + $sql = 'SELECT forum_id, forum_name, forum_password, forum_type, forum_options + FROM ' . FORUMS_TABLE . ' + WHERE forum_id = ' . $this->forum_id; + $result = $this->db->sql_query($sql); + $this->forum_data = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if (empty($this->forum_data)) + { + trigger_error('NO_FORUM'); + } + + // Forum needs to be postable + if ($this->forum_data['forum_type'] != FORUM_POST) + { + trigger_error('NO_FEED'); + } + + // Make sure forum is not excluded from feed + if (phpbb_optionget(FORUM_OPTION_FEED_EXCLUDE, $this->forum_data['forum_options'])) + { + trigger_error('NO_FEED'); + } + + // Make sure we can read this forum + if (!$this->auth->acl_get('f_read', $this->forum_id)) + { + trigger_error('SORRY_AUTH_READ'); + } + + // Make sure forum is not passworded or user is authed + if ($this->forum_data['forum_password']) + { + $forum_ids_passworded = $this->get_passworded_forums(); + + if (isset($forum_ids_passworded[$this->forum_id])) + { + trigger_error('SORRY_AUTH_READ'); + } + + unset($forum_ids_passworded); + } + } + + function get_sql() + { + // Determine topics with recent activity + $sql = 'SELECT topic_id, topic_last_post_time + FROM ' . TOPICS_TABLE . ' + WHERE forum_id = ' . $this->forum_id . ' + AND topic_moved_id = 0 + AND ' . $this->content_visibility->get_visibility_sql('topic', $this->forum_id) . ' + ORDER BY topic_last_post_time DESC'; + $result = $this->db->sql_query_limit($sql, $this->num_items); + + $topic_ids = array(); + $min_post_time = 0; + while ($row = $this->db->sql_fetchrow()) + { + $topic_ids[] = (int) $row['topic_id']; + + $min_post_time = (int) $row['topic_last_post_time']; + } + $this->db->sql_freeresult($result); + + if (empty($topic_ids)) + { + return false; + } + + $this->sql = array( + 'SELECT' => 'p.post_id, p.topic_id, p.post_time, p.post_edit_time, p.post_visibility, p.post_subject, p.post_text, p.bbcode_bitfield, p.bbcode_uid, p.enable_bbcode, p.enable_smilies, p.enable_magic_url, ' . + 'u.username, u.user_id', + 'FROM' => array( + POSTS_TABLE => 'p', + USERS_TABLE => 'u', + ), + 'WHERE' => $this->db->sql_in_set('p.topic_id', $topic_ids) . ' + AND ' . $this->content_visibility->get_visibility_sql('post', $this->forum_id, 'p.') . ' + AND p.post_time >= ' . $min_post_time . ' + AND p.poster_id = u.user_id', + 'ORDER_BY' => 'p.post_time DESC', + ); + + return true; + } + + function adjust_item(&$item_row, &$row) + { + parent::adjust_item($item_row, $row); + + $item_row['title'] = (isset($row['forum_name']) && $row['forum_name'] !== '') ? $row['forum_name'] . ' ' . $this->separator . ' ' . $item_row['title'] : $item_row['title']; + } + + function get_item() + { + return ($row = parent::get_item()) ? array_merge($this->forum_data, $row) : $row; + } +} diff --git a/phpBB/phpbb/feed/forums.php b/phpBB/phpbb/feed/forums.php new file mode 100644 index 0000000000..409097a9f3 --- /dev/null +++ b/phpBB/phpbb/feed/forums.php @@ -0,0 +1,72 @@ +set('title', 'forum_name'); + $this->set('text', 'forum_desc'); + $this->set('bitfield', 'forum_desc_bitfield'); + $this->set('bbcode_uid','forum_desc_uid'); + $this->set('updated', 'forum_last_post_time'); + $this->set('options', 'forum_desc_options'); + } + + function get_sql() + { + $in_fid_ary = array_diff($this->get_readable_forums(), $this->get_excluded_forums()); + if (empty($in_fid_ary)) + { + return false; + } + + // Build SQL Query + $this->sql = array( + 'SELECT' => 'f.forum_id, f.left_id, f.forum_name, f.forum_last_post_time, + f.forum_desc, f.forum_desc_bitfield, f.forum_desc_uid, f.forum_desc_options, + f.forum_topics_approved, f.forum_posts_approved', + 'FROM' => array(FORUMS_TABLE => 'f'), + 'WHERE' => 'f.forum_type = ' . FORUM_POST . ' + AND ' . $this->db->sql_in_set('f.forum_id', $in_fid_ary), + 'ORDER_BY' => 'f.left_id ASC', + ); + + return true; + } + + function adjust_item(&$item_row, &$row) + { + $item_row['link'] = $this->helper->append_sid('viewforum.' . $this->phpEx, 'f=' . $row['forum_id']); + + if ($this->config['feed_item_statistics']) + { + $item_row['statistics'] = $this->user->lang('TOTAL_TOPICS', (int) $row['forum_topics_approved']) + . ' ' . $this->separator_stats . ' ' . $this->user->lang('TOTAL_POSTS_COUNT', (int) $row['forum_posts_approved']); + } + } +} diff --git a/phpBB/phpbb/feed/helper.php b/phpBB/phpbb/feed/helper.php new file mode 100644 index 0000000000..93330aa2ad --- /dev/null +++ b/phpBB/phpbb/feed/helper.php @@ -0,0 +1,159 @@ +config = $config; + $this->user = $user; + $this->phpbb_root_path = $phpbb_root_path; + } + + /** + * Run links through append_sid(), prepend generate_board_url() and remove session id + */ + public function get_board_url() + { + static $board_url; + + if (empty($board_url)) + { + $board_url = generate_board_url(); + } + + return $board_url; + } + + /** + * Run links through append_sid(), prepend generate_board_url() and remove session id + */ + public function append_sid($url, $params) + { + return append_sid($this->get_board_url() . '/' . $url, $params, true, ''); + } + + /** + * Generate ISO 8601 date string (RFC 3339) + */ + public function format_date($time) + { + static $zone_offset; + static $offset_string; + + if (empty($offset_string)) + { + $zone_offset = $this->user->create_datetime()->getOffset(); + $offset_string = phpbb_format_timezone_offset($zone_offset); + } + + return gmdate("Y-m-d\TH:i:s", $time + $zone_offset) . $offset_string; + } + + /** + * Generate text content + */ + public function generate_content($content, $uid, $bitfield, $options) + { + if (empty($content)) + { + return ''; + } + + // Prepare some bbcodes for better parsing + $content = preg_replace("#\[quote(=".*?")?:$uid\]\s*(.*?)\s*\[/quote:$uid\]#si", "[quote$1:$uid]
$2
[/quote:$uid]", $content); + + $content = generate_text_for_display($content, $uid, $bitfield, $options); + + // Add newlines + $content = str_replace('
', '
' . "\n", $content); + + // Convert smiley Relative paths to Absolute path, Windows style + $content = str_replace($this->phpbb_root_path . $this->config['smilies_path'], $this->get_board_url() . '/' . $this->config['smilies_path'], $content); + + // Remove "Select all" link and mouse events + $content = str_replace('' . $this->user->lang['SELECT_ALL_CODE'] . '', '', $content); + $content = preg_replace('#(onkeypress|onclick)="(.*?)"#si', '', $content); + + // Firefox does not support CSS for feeds, though + + // Remove font sizes + // $content = preg_replace('#([^>]+)#iU', '\1', $content); + + // Make text strong :P + // $content = preg_replace('#(.*?)#iU', '\1', $content); + + // Italic + // $content = preg_replace('#([^<]+)#iU', '\1', $content); + + // Underline + // $content = preg_replace('#([^<]+)#iU', '\1', $content); + + // Remove embed Windows Media Streams + $content = preg_replace( '#<\!--\[if \!IE\]>-->([^[]+)<\!--#si', '', $content); + + // Do not use < and >, because we want to retain code contained in [code][/code] + + // Remove embed and objects + $content = preg_replace( '#<(object|embed)(.*?) (value|src)=(.*?) ([^[]+)(object|embed)>#si',' $1 ',$content); + + // Remove some specials html tag, because somewhere there are a mod to allow html tags ;) + $content = preg_replace( '#<(script|iframe)([^[]+)\1>#siU', ' $1 ', $content); + + // Remove Comments from inline attachments [ia] + $content = preg_replace('#
(.*?)(.*?)(.*?)
#si','$4',$content); + + // Replace some entities with their unicode counterpart + $entities = array( + ' ' => "\xC2\xA0", + '•' => "\xE2\x80\xA2", + '·' => "\xC2\xB7", + '©' => "\xC2\xA9", + ); + + $content = str_replace(array_keys($entities), array_values($entities), $content); + + // Remove CDATA blocks. ;) + $content = preg_replace('#\<\!\[CDATA\[(.*?)\]\]\>#s', '', $content); + + // Other control characters + $content = preg_replace('#(?:[\x00-\x1F\x7F]+|(?:\xC2[\x80-\x9F])+)#', '', $content); + + return $content; + } +} diff --git a/phpBB/phpbb/feed/news.php b/phpBB/phpbb/feed/news.php new file mode 100644 index 0000000000..f2d45b5165 --- /dev/null +++ b/phpBB/phpbb/feed/news.php @@ -0,0 +1,112 @@ +num_items} first posts +* of all topics in the selected news forums. +* +* @package phpBB3 +*/ +class phpbb_feed_news extends phpbb_feed_topic_base +{ + function get_news_forums() + { + static $forum_ids; + + // Matches acp/acp_board.php + $cache_name = 'feed_news_forum_ids'; + + if (!isset($forum_ids) && ($forum_ids = $this->cache->get('_' . $cache_name)) === false) + { + $sql = 'SELECT forum_id + FROM ' . FORUMS_TABLE . ' + WHERE ' . $this->db->sql_bit_and('forum_options', FORUM_OPTION_FEED_NEWS, '<> 0'); + $result = $this->db->sql_query($sql); + + $forum_ids = array(); + while ($forum_id = (int) $this->db->sql_fetchfield('forum_id')) + { + $forum_ids[$forum_id] = $forum_id; + } + $this->db->sql_freeresult($result); + + $this->cache->put('_' . $cache_name, $forum_ids); + } + + return $forum_ids; + } + + function get_sql() + { + // Determine forum ids + $in_fid_ary = array_intersect($this->get_news_forums(), $this->get_readable_forums()); + if (empty($in_fid_ary)) + { + return false; + } + + $in_fid_ary = array_diff($in_fid_ary, $this->get_passworded_forums()); + if (empty($in_fid_ary)) + { + return false; + } + + // We really have to get the post ids first! + $sql = 'SELECT topic_first_post_id, topic_time + FROM ' . TOPICS_TABLE . ' + WHERE ' . $this->db->sql_in_set('forum_id', $in_fid_ary) . ' + AND topic_moved_id = 0 + AND topic_visibility = ' . ITEM_APPROVED . ' + ORDER BY topic_time DESC'; + $result = $this->db->sql_query_limit($sql, $this->num_items); + + $post_ids = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $post_ids[] = (int) $row['topic_first_post_id']; + } + $this->db->sql_freeresult($result); + + if (empty($post_ids)) + { + return false; + } + + $this->sql = array( + 'SELECT' => 'f.forum_id, f.forum_name, + t.topic_id, t.topic_title, t.topic_poster, t.topic_first_poster_name, t.topic_posts_approved, t.topic_posts_unapproved, t.topic_posts_softdeleted, t.topic_views, t.topic_time, t.topic_last_post_time, + p.post_id, p.post_time, p.post_edit_time, p.post_text, p.bbcode_bitfield, p.bbcode_uid, p.enable_bbcode, p.enable_smilies, p.enable_magic_url', + 'FROM' => array( + TOPICS_TABLE => 't', + POSTS_TABLE => 'p', + ), + 'LEFT_JOIN' => array( + array( + 'FROM' => array(FORUMS_TABLE => 'f'), + 'ON' => 'p.forum_id = f.forum_id', + ), + ), + 'WHERE' => 'p.topic_id = t.topic_id + AND ' . $this->db->sql_in_set('p.post_id', $post_ids), + 'ORDER_BY' => 'p.post_time DESC', + ); + + return true; + } +} diff --git a/phpBB/phpbb/feed/overall.php b/phpBB/phpbb/feed/overall.php new file mode 100644 index 0000000000..869df7cde0 --- /dev/null +++ b/phpBB/phpbb/feed/overall.php @@ -0,0 +1,90 @@ +num_items} posts +* from the whole board. +* +* @package phpBB3 +*/ +class phpbb_feed_overall extends phpbb_feed_post_base +{ + function get_sql() + { + $forum_ids = array_diff($this->get_readable_forums(), $this->get_excluded_forums(), $this->get_passworded_forums()); + if (empty($forum_ids)) + { + return false; + } + + // Determine topics with recent activity + $sql = 'SELECT topic_id, topic_last_post_time + FROM ' . TOPICS_TABLE . ' + WHERE topic_moved_id = 0 + AND ' . $this->content_visibility->get_forums_visibility_sql('topic', $forum_ids) . ' + ORDER BY topic_last_post_time DESC'; + $result = $this->db->sql_query_limit($sql, $this->num_items); + + $topic_ids = array(); + $min_post_time = 0; + while ($row = $this->db->sql_fetchrow()) + { + $topic_ids[] = (int) $row['topic_id']; + + $min_post_time = (int) $row['topic_last_post_time']; + } + $this->db->sql_freeresult($result); + + if (empty($topic_ids)) + { + return false; + } + + // Get the actual data + $this->sql = array( + 'SELECT' => 'f.forum_id, f.forum_name, ' . + 'p.post_id, p.topic_id, p.post_time, p.post_edit_time, p.post_visibility, p.post_subject, p.post_text, p.bbcode_bitfield, p.bbcode_uid, p.enable_bbcode, p.enable_smilies, p.enable_magic_url, ' . + 'u.username, u.user_id', + 'FROM' => array( + USERS_TABLE => 'u', + POSTS_TABLE => 'p', + ), + 'LEFT_JOIN' => array( + array( + 'FROM' => array(FORUMS_TABLE => 'f'), + 'ON' => 'f.forum_id = p.forum_id', + ), + ), + 'WHERE' => $this->db->sql_in_set('p.topic_id', $topic_ids) . ' + AND ' . $this->content_visibility->get_visibility_sql('post', array(), 'p.') . ' + AND p.post_time >= ' . $min_post_time . ' + AND u.user_id = p.poster_id', + 'ORDER_BY' => 'p.post_time DESC', + ); + + return true; + } + + function adjust_item(&$item_row, &$row) + { + parent::adjust_item($item_row, $row); + + $item_row['title'] = (isset($row['forum_name']) && $row['forum_name'] !== '') ? $row['forum_name'] . ' ' . $this->separator . ' ' . $item_row['title'] : $item_row['title']; + } +} diff --git a/phpBB/phpbb/feed/post_base.php b/phpBB/phpbb/feed/post_base.php new file mode 100644 index 0000000000..1f4cb4b5ef --- /dev/null +++ b/phpBB/phpbb/feed/post_base.php @@ -0,0 +1,57 @@ +set('title', 'post_subject'); + $this->set('title2', 'topic_title'); + + $this->set('author_id', 'user_id'); + $this->set('creator', 'username'); + $this->set('published', 'post_time'); + $this->set('updated', 'post_edit_time'); + $this->set('text', 'post_text'); + + $this->set('bitfield', 'bbcode_bitfield'); + $this->set('bbcode_uid','bbcode_uid'); + + $this->set('enable_bbcode', 'enable_bbcode'); + $this->set('enable_smilies', 'enable_smilies'); + $this->set('enable_magic_url', 'enable_magic_url'); + } + + function adjust_item(&$item_row, &$row) + { + $item_row['link'] = $this->helper->append_sid('viewtopic.' . $this->phpEx, "t={$row['topic_id']}&p={$row['post_id']}#p{$row['post_id']}"); + + if ($this->config['feed_item_statistics']) + { + $item_row['statistics'] = $this->user->lang['POSTED'] . ' ' . $this->user->lang['POST_BY_AUTHOR'] . ' ' . $this->user_viewprofile($row) + . ' ' . $this->separator_stats . ' ' . $this->user->format_date($row[$this->get('published')]) + . (($this->is_moderator_approve_forum($row['forum_id']) && $row['post_visibility'] !== ITEM_APPROVED) ? ' ' . $this->separator_stats . ' ' . $this->user->lang['POST_UNAPPROVED'] : ''); + } + } +} diff --git a/phpBB/phpbb/feed/topic.php b/phpBB/phpbb/feed/topic.php new file mode 100644 index 0000000000..36f958ac60 --- /dev/null +++ b/phpBB/phpbb/feed/topic.php @@ -0,0 +1,116 @@ +num_items} posts made within this topic. +* +* @package phpBB3 +*/ +class phpbb_feed_topic extends phpbb_feed_post_base +{ + var $topic_id = 0; + var $forum_id = 0; + var $topic_data = array(); + + /** + * Set the Topic ID + * + * @param int $topic_id Topic ID + * @return phpbb_feed_topic + */ + public function set_topic_id($topic_id) + { + $this->topic_id = (int) $topic_id; + + return $this; + } + + function open() + { + $sql = 'SELECT f.forum_options, f.forum_password, t.topic_id, t.forum_id, t.topic_visibility, t.topic_title, t.topic_time, t.topic_views, t.topic_replies, t.topic_type + FROM ' . TOPICS_TABLE . ' t + LEFT JOIN ' . FORUMS_TABLE . ' f + ON (f.forum_id = t.forum_id) + WHERE t.topic_id = ' . $this->topic_id; + $result = $this->db->sql_query($sql); + $this->topic_data = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if (empty($this->topic_data)) + { + trigger_error('NO_TOPIC'); + } + + $this->forum_id = (int) $this->topic_data['forum_id']; + + // Make sure topic is either approved or user authed + if (!$this->topic_data['topic_approved'] && !$this->auth->acl_get('m_approve', $this->forum_id)) + { + trigger_error('SORRY_AUTH_READ'); + } + + // Make sure forum is not excluded from feed + if (phpbb_optionget(FORUM_OPTION_FEED_EXCLUDE, $this->topic_data['forum_options'])) + { + trigger_error('NO_FEED'); + } + + // Make sure we can read this forum + if (!$this->auth->acl_get('f_read', $this->forum_id)) + { + trigger_error('SORRY_AUTH_READ'); + } + + // Make sure forum is not passworded or user is authed + if ($this->topic_data['forum_password']) + { + $forum_ids_passworded = $this->get_passworded_forums(); + + if (isset($forum_ids_passworded[$this->forum_id])) + { + trigger_error('SORRY_AUTH_READ'); + } + + unset($forum_ids_passworded); + } + } + + function get_sql() + { + $this->sql = array( + 'SELECT' => 'p.post_id, p.post_time, p.post_edit_time, p.post_visibility, p.post_subject, p.post_text, p.bbcode_bitfield, p.bbcode_uid, p.enable_bbcode, p.enable_smilies, p.enable_magic_url, ' . + 'u.username, u.user_id', + 'FROM' => array( + POSTS_TABLE => 'p', + USERS_TABLE => 'u', + ), + 'WHERE' => 'p.topic_id = ' . $this->topic_id . ' + AND ' . $this->content_visibility->get_visibility_sql('post', $this->forum_id, 'p.') . ' + AND p.poster_id = u.user_id', + 'ORDER_BY' => 'p.post_time DESC', + ); + + return true; + } + + function get_item() + { + return ($row = parent::get_item()) ? array_merge($this->topic_data, $row) : $row; + } +} diff --git a/phpBB/phpbb/feed/topic_base.php b/phpBB/phpbb/feed/topic_base.php new file mode 100644 index 0000000000..b104a46631 --- /dev/null +++ b/phpBB/phpbb/feed/topic_base.php @@ -0,0 +1,59 @@ +set('title', 'topic_title'); + $this->set('title2', 'forum_name'); + + $this->set('author_id', 'topic_poster'); + $this->set('creator', 'topic_first_poster_name'); + $this->set('published', 'post_time'); + $this->set('updated', 'post_edit_time'); + $this->set('text', 'post_text'); + + $this->set('bitfield', 'bbcode_bitfield'); + $this->set('bbcode_uid','bbcode_uid'); + + $this->set('enable_bbcode', 'enable_bbcode'); + $this->set('enable_smilies', 'enable_smilies'); + $this->set('enable_magic_url', 'enable_magic_url'); + } + + function adjust_item(&$item_row, &$row) + { + $item_row['link'] = $this->helper->append_sid('viewtopic.' . $this->phpEx, 't=' . $row['topic_id'] . '&p=' . $row['post_id'] . '#p' . $row['post_id']); + + if ($this->config['feed_item_statistics']) + { + $item_row['statistics'] = $this->user->lang['POSTED'] . ' ' . $this->user->lang['POST_BY_AUTHOR'] . ' ' . $this->user_viewprofile($row) + . ' ' . $this->separator_stats . ' ' . $this->user->format_date($row[$this->get('published')]) + . ' ' . $this->separator_stats . ' ' . $this->user->lang['REPLIES'] . ' ' . $this->content_visibility->get_count('topic_posts', $row, $row['forum_id']) - 1 + . ' ' . $this->separator_stats . ' ' . $this->user->lang['VIEWS'] . ' ' . $row['topic_views'] + . (($this->is_moderator_approve_forum($row['forum_id']) && $row['topic_posts_unapproved']) ? ' ' . $this->separator_stats . ' ' . $this->user->lang['POSTS_UNAPPROVED'] : ''); + } + } +} diff --git a/phpBB/phpbb/feed/topics.php b/phpBB/phpbb/feed/topics.php new file mode 100644 index 0000000000..31f5177773 --- /dev/null +++ b/phpBB/phpbb/feed/topics.php @@ -0,0 +1,91 @@ +num_items} created topics +* including the first post. +* +* @package phpBB3 +*/ +class phpbb_feed_topics extends phpbb_feed_topic_base +{ + function get_sql() + { + $forum_ids_read = $this->get_readable_forums(); + if (empty($forum_ids_read)) + { + return false; + } + + $in_fid_ary = array_diff($forum_ids_read, $this->get_excluded_forums(), $this->get_passworded_forums()); + if (empty($in_fid_ary)) + { + return false; + } + + // We really have to get the post ids first! + $sql = 'SELECT topic_first_post_id, topic_time + FROM ' . TOPICS_TABLE . ' + WHERE ' . $this->db->sql_in_set('forum_id', $in_fid_ary) . ' + AND topic_moved_id = 0 + AND topic_visibility = ' . ITEM_APPROVED . ' + ORDER BY topic_time DESC'; + $result = $this->db->sql_query_limit($sql, $this->num_items); + + $post_ids = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $post_ids[] = (int) $row['topic_first_post_id']; + } + $this->db->sql_freeresult($result); + + if (empty($post_ids)) + { + return false; + } + + $this->sql = array( + 'SELECT' => 'f.forum_id, f.forum_name, + t.topic_id, t.topic_title, t.topic_poster, t.topic_first_poster_name, t.topic_posts_approved, t.topic_posts_unapproved, t.topic_posts_softdeleted, t.topic_views, t.topic_time, t.topic_last_post_time, + p.post_id, p.post_time, p.post_edit_time, p.post_text, p.bbcode_bitfield, p.bbcode_uid, p.enable_bbcode, p.enable_smilies, p.enable_magic_url', + 'FROM' => array( + TOPICS_TABLE => 't', + POSTS_TABLE => 'p', + ), + 'LEFT_JOIN' => array( + array( + 'FROM' => array(FORUMS_TABLE => 'f'), + 'ON' => 'p.forum_id = f.forum_id', + ), + ), + 'WHERE' => 'p.topic_id = t.topic_id + AND ' . $this->db->sql_in_set('p.post_id', $post_ids), + 'ORDER_BY' => 'p.post_time DESC', + ); + + return true; + } + + function adjust_item(&$item_row, &$row) + { + parent::adjust_item($item_row, $row); + + $item_row['title'] = (isset($row['forum_name']) && $row['forum_name'] !== '') ? $row['forum_name'] . ' ' . $this->separator . ' ' . $item_row['title'] : $item_row['title']; + } +} diff --git a/phpBB/phpbb/feed/topics_active.php b/phpBB/phpbb/feed/topics_active.php new file mode 100644 index 0000000000..249dd1d66a --- /dev/null +++ b/phpBB/phpbb/feed/topics_active.php @@ -0,0 +1,136 @@ +num_items} topics +* with replies made withing the last {$this->sort_days} days +* including the last post. +* +* @package phpBB3 +*/ +class phpbb_feed_topics_active extends phpbb_feed_topic_base +{ + var $sort_days = 7; + + function set_keys() + { + parent::set_keys(); + + $this->set('author_id', 'topic_last_poster_id'); + $this->set('creator', 'topic_last_poster_name'); + } + + function get_sql() + { + $forum_ids_read = $this->get_readable_forums(); + if (empty($forum_ids_read)) + { + return false; + } + + $in_fid_ary = array_intersect($forum_ids_read, $this->get_forum_ids()); + $in_fid_ary = array_diff($in_fid_ary, $this->get_passworded_forums()); + if (empty($in_fid_ary)) + { + return false; + } + + // Search for topics in last X days + $last_post_time_sql = ($this->sort_days) ? ' AND topic_last_post_time > ' . (time() - ($this->sort_days * 24 * 3600)) : ''; + + // We really have to get the post ids first! + $sql = 'SELECT topic_last_post_id, topic_last_post_time + FROM ' . TOPICS_TABLE . ' + WHERE ' . $this->db->sql_in_set('forum_id', $in_fid_ary) . ' + AND topic_moved_id = 0 + AND topic_visibility = ' . ITEM_APPROVED . ' + ' . $last_post_time_sql . ' + ORDER BY topic_last_post_time DESC'; + $result = $this->db->sql_query_limit($sql, $this->num_items); + + $post_ids = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $post_ids[] = (int) $row['topic_last_post_id']; + } + $this->db->sql_freeresult($result); + + if (empty($post_ids)) + { + return false; + } + + $this->sql = array( + 'SELECT' => 'f.forum_id, f.forum_name, + t.topic_id, t.topic_title, t.topic_posts_approved, t.topic_posts_unapproved, t.topic_posts_softdeleted, t.topic_views, + t.topic_last_poster_id, t.topic_last_poster_name, t.topic_last_post_time, + p.post_id, p.post_time, p.post_edit_time, p.post_text, p.bbcode_bitfield, p.bbcode_uid, p.enable_bbcode, p.enable_smilies, p.enable_magic_url', + 'FROM' => array( + TOPICS_TABLE => 't', + POSTS_TABLE => 'p', + ), + 'LEFT_JOIN' => array( + array( + 'FROM' => array(FORUMS_TABLE => 'f'), + 'ON' => 'p.forum_id = f.forum_id', + ), + ), + 'WHERE' => 'p.topic_id = t.topic_id + AND ' . $this->db->sql_in_set('p.post_id', $post_ids), + 'ORDER_BY' => 'p.post_time DESC', + ); + + return true; + } + + function get_forum_ids() + { + static $forum_ids; + + $cache_name = 'feed_topic_active_forum_ids'; + + if (!isset($forum_ids) && ($forum_ids = $this->cache->get('_' . $cache_name)) === false) + { + $sql = 'SELECT forum_id + FROM ' . FORUMS_TABLE . ' + WHERE forum_type = ' . FORUM_POST . ' + AND ' . $this->db->sql_bit_and('forum_options', FORUM_OPTION_FEED_EXCLUDE, '= 0') . ' + AND ' . $this->db->sql_bit_and('forum_flags', log(FORUM_FLAG_ACTIVE_TOPICS, 2), '<> 0'); + $result = $this->db->sql_query($sql); + + $forum_ids = array(); + while ($forum_id = (int) $this->db->sql_fetchfield('forum_id')) + { + $forum_ids[$forum_id] = $forum_id; + } + $this->db->sql_freeresult($result); + + $this->cache->put('_' . $cache_name, $forum_ids, 180); + } + + return $forum_ids; + } + + function adjust_item(&$item_row, &$row) + { + parent::adjust_item($item_row, $row); + + $item_row['title'] = (isset($row['forum_name']) && $row['forum_name'] !== '') ? $row['forum_name'] . ' ' . $this->separator . ' ' . $item_row['title'] : $item_row['title']; + } +} diff --git a/phpBB/phpbb/filesystem.php b/phpBB/phpbb/filesystem.php new file mode 100644 index 0000000000..27cab48fb0 --- /dev/null +++ b/phpBB/phpbb/filesystem.php @@ -0,0 +1,52 @@ +db = $db; + $this->user = $user; + } + + /** + * Returns the group_legend for a given group, if the group exists. + * + * {@inheritDoc} + */ + public function get_group_value($group_id) + { + $sql = 'SELECT group_legend + FROM ' . GROUPS_TABLE . ' + WHERE group_id = ' . (int) $group_id; + $result = $this->db->sql_query($sql); + $current_value = $this->db->sql_fetchfield('group_legend'); + $this->db->sql_freeresult($result); + + if ($current_value === false) + { + // Group not found. + throw new phpbb_groupposition_exception('NO_GROUP'); + } + + return (int) $current_value; + } + + /** + * Get number of groups, displayed on the legend + * + * {@inheritDoc} + */ + public function get_group_count() + { + $sql = 'SELECT group_legend + FROM ' . GROUPS_TABLE . ' + ORDER BY group_legend DESC'; + $result = $this->db->sql_query_limit($sql, 1); + $group_count = (int) $this->db->sql_fetchfield('group_legend'); + $this->db->sql_freeresult($result); + + return $group_count; + } + + /** + * Adds a group by group_id + * + * {@inheritDoc} + */ + public function add_group($group_id) + { + $current_value = $this->get_group_value($group_id); + + if ($current_value == self::GROUP_DISABLED) + { + // Group is currently not displayed, add it at the end. + $next_value = 1 + $this->get_group_count(); + + $sql = 'UPDATE ' . GROUPS_TABLE . ' + SET group_legend = ' . $next_value . ' + WHERE group_legend = ' . self::GROUP_DISABLED . ' + AND group_id = ' . (int) $group_id; + $this->db->sql_query($sql); + return true; + } + + return false; + } + + /** + * Deletes a group by setting the field to self::GROUP_DISABLED and closing the gap in the list. + * + * {@inheritDoc} + */ + public function delete_group($group_id, $skip_group = false) + { + $current_value = $this->get_group_value($group_id); + + if ($current_value != self::GROUP_DISABLED) + { + $this->db->sql_transaction('begin'); + + $sql = 'UPDATE ' . GROUPS_TABLE . ' + SET group_legend = group_legend - 1 + WHERE group_legend > ' . $current_value; + $this->db->sql_query($sql); + + if (!$skip_group) + { + $sql = 'UPDATE ' . GROUPS_TABLE . ' + SET group_legend = ' . self::GROUP_DISABLED . ' + WHERE group_id = ' . (int) $group_id; + $this->db->sql_query($sql); + } + + $this->db->sql_transaction('commit'); + + return true; + } + + return false; + } + + /** + * Moves a group up by group_id + * + * {@inheritDoc} + */ + public function move_up($group_id) + { + return $this->move($group_id, 1); + } + + /** + * Moves a group down by group_id + * + * {@inheritDoc} + */ + public function move_down($group_id) + { + return $this->move($group_id, -1); + } + + /** + * Moves a group up/down + * + * {@inheritDoc} + */ + public function move($group_id, $delta) + { + $delta = (int) $delta; + if (!$delta) + { + return false; + } + + $move_up = ($delta > 0) ? true : false; + $current_value = $this->get_group_value($group_id); + + if ($current_value != self::GROUP_DISABLED) + { + $this->db->sql_transaction('begin'); + + // First we move all groups between our current value and the target value up/down 1, + // so we have a gap for our group to move. + $sql = 'UPDATE ' . GROUPS_TABLE . ' + SET group_legend = group_legend' . (($move_up) ? ' + 1' : ' - 1') . ' + WHERE group_legend > ' . self::GROUP_DISABLED . ' + AND group_legend' . (($move_up) ? ' >= ' : ' <= ') . ($current_value - $delta) . ' + AND group_legend' . (($move_up) ? ' < ' : ' > ') . $current_value; + $this->db->sql_query($sql); + + // Because there might be fewer groups above/below the group than we wanted to move, + // we use the number of changed groups, to update the group. + $delta = (int) $this->db->sql_affectedrows(); + + if ($delta) + { + // And now finally, when we moved some other groups and built a gap, + // we can move the desired group to it. + $sql = 'UPDATE ' . GROUPS_TABLE . ' + SET group_legend = group_legend ' . (($move_up) ? ' - ' : ' + ') . $delta . ' + WHERE group_id = ' . (int) $group_id; + $this->db->sql_query($sql); + + $this->db->sql_transaction('commit'); + + return true; + } + + $this->db->sql_transaction('commit'); + } + + return false; + } + + /** + * Get group type language var + * + * @param int $group_type group_type from the groups-table + * @return string name of the language variable for the given group-type. + */ + static public function group_type_language($group_type) + { + switch ($group_type) + { + case GROUP_OPEN: + return 'GROUP_REQUEST'; + case GROUP_CLOSED: + return 'GROUP_CLOSED'; + case GROUP_HIDDEN: + return 'GROUP_HIDDEN'; + case GROUP_SPECIAL: + return 'GROUP_SPECIAL'; + case GROUP_FREE: + return 'GROUP_OPEN'; + } + } +} diff --git a/phpBB/phpbb/groupposition/teampage.php b/phpBB/phpbb/groupposition/teampage.php new file mode 100644 index 0000000000..7c758199e7 --- /dev/null +++ b/phpBB/phpbb/groupposition/teampage.php @@ -0,0 +1,604 @@ +db = $db; + $this->user = $user; + $this->cache = $cache; + } + + /** + * Returns the teampage position for a given group, if the group exists. + * + * {@inheritDoc} + */ + public function get_group_value($group_id) + { + // The join is required to ensure that the group itself exists + $sql = 'SELECT g.group_id, t.teampage_position + FROM ' . GROUPS_TABLE . ' g + LEFT JOIN ' . TEAMPAGE_TABLE . ' t + ON (t.group_id = g.group_id) + WHERE g.group_id = ' . (int) $group_id; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if ($row === false) + { + // Group not found. + throw new phpbb_groupposition_exception('NO_GROUP'); + } + + return (int) $row['teampage_position']; + } + + /** + * Returns the row for a given group, if the group exists. + * + * @param int $group_id group_id of the group to be selected + * @return array Data row of the group + */ + public function get_group_values($group_id) + { + // The join is required to ensure that the group itself exists + $sql = 'SELECT * + FROM ' . GROUPS_TABLE . ' g + LEFT JOIN ' . TEAMPAGE_TABLE . ' t + ON (t.group_id = g.group_id) + WHERE g.group_id = ' . (int) $group_id; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if ($row === false) + { + // Group not found. + throw new phpbb_groupposition_exception('NO_GROUP'); + } + + return $row; + } + + /** + * Returns the teampage position for a given teampage item, if the item exists. + * + * @param int $teampage_id Teampage_id of the selected item + * @return int Teampage position of the item + */ + public function get_teampage_value($teampage_id) + { + $sql = 'SELECT teampage_position + FROM ' . TEAMPAGE_TABLE . ' + WHERE teampage_id = ' . (int) $teampage_id; + $result = $this->db->sql_query($sql); + $current_value = $this->db->sql_fetchfield('teampage_position'); + $this->db->sql_freeresult($result); + + if ($current_value === false) + { + // Group not found. + throw new phpbb_groupposition_exception('NO_GROUP'); + } + + return (int) $current_value; + } + + /** + * Returns the teampage row for a given teampage item, if the item exists. + * + * @param int $teampage_id Teampage_id of the selected item + * @return array Teampage row of the item + */ + public function get_teampage_values($teampage_id) + { + $sql = 'SELECT teampage_position, teampage_parent + FROM ' . TEAMPAGE_TABLE . ' + WHERE teampage_id = ' . (int) $teampage_id; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if ($row === false) + { + // Group not found. + throw new phpbb_groupposition_exception('NO_GROUP'); + } + + return $row; + } + + + /** + * Get number of items displayed + * + * {@inheritDoc} + */ + public function get_group_count() + { + $sql = 'SELECT teampage_position + FROM ' . TEAMPAGE_TABLE . ' + ORDER BY teampage_position DESC'; + $result = $this->db->sql_query_limit($sql, 1); + $group_count = (int) $this->db->sql_fetchfield('teampage_position'); + $this->db->sql_freeresult($result); + + return $group_count; + } + + /** + * Adds a group by group_id + * + * {@inheritDoc} + */ + public function add_group($group_id) + { + return $this->add_group_teampage($group_id, self::NO_PARENT); + } + + /** + * Adds a group by group_id + * + * @param int $group_id group_id of the group to be added + * @param int $parent_id Teampage ID of the parent item + * @return bool True if the group was added successfully + */ + public function add_group_teampage($group_id, $parent_id) + { + $current_value = $this->get_group_value($group_id); + + if ($current_value == self::GROUP_DISABLED) + { + if ($parent_id != self::NO_PARENT) + { + // Check, whether the given parent is a category + $sql = 'SELECT teampage_id + FROM ' . TEAMPAGE_TABLE . ' + WHERE group_id = 0 + AND teampage_id = ' . (int) $parent_id; + $result = $this->db->sql_query_limit($sql, 1); + $parent_is_category = (bool) $this->db->sql_fetchfield('teampage_id'); + $this->db->sql_freeresult($result); + + if ($parent_is_category) + { + // Get value of last child from this parent and add group there + $sql = 'SELECT teampage_position + FROM ' . TEAMPAGE_TABLE . ' + WHERE teampage_parent = ' . (int) $parent_id . ' + OR teampage_id = ' . (int) $parent_id . ' + ORDER BY teampage_position DESC'; + $result = $this->db->sql_query_limit($sql, 1); + $new_position = (int) $this->db->sql_fetchfield('teampage_position'); + $this->db->sql_freeresult($result); + + $sql = 'UPDATE ' . TEAMPAGE_TABLE . ' + SET teampage_position = teampage_position + 1 + WHERE teampage_position > ' . $new_position; + $this->db->sql_query($sql); + } + } + else + { + // Add group at the end + $new_position = $this->get_group_count(); + } + + $sql_ary = array( + 'group_id' => $group_id, + 'teampage_position' => $new_position + 1, + 'teampage_parent' => $parent_id, + ); + + $sql = 'INSERT INTO ' . TEAMPAGE_TABLE . ' ' . $this->db->sql_build_array('INSERT', $sql_ary); + $this->db->sql_query($sql); + + $this->cache->destroy('sql', TEAMPAGE_TABLE); + return true; + } + + $this->cache->destroy('sql', TEAMPAGE_TABLE); + return false; + } + + /** + * Adds a new category + * + * @param string $category_name Name of the category to be added + * @return bool True if the category was added successfully + */ + public function add_category_teampage($category_name) + { + if ($category_name === '') + { + return false; + } + + $num_entries = $this->get_group_count(); + + $sql_ary = array( + 'group_id' => 0, + 'teampage_position' => $num_entries + 1, + 'teampage_parent' => 0, + 'teampage_name' => truncate_string($category_name, 255, 255), + ); + + $sql = 'INSERT INTO ' . TEAMPAGE_TABLE . ' ' . $this->db->sql_build_array('INSERT', $sql_ary); + $this->db->sql_query($sql); + + $this->cache->destroy('sql', TEAMPAGE_TABLE); + return true; + } + + /** + * Deletes a group from the list and closes the gap in the position list. + * + * {@inheritDoc} + */ + public function delete_group($group_id, $skip_group = false) + { + $current_value = $this->get_group_value($group_id); + + if ($current_value != self::GROUP_DISABLED) + { + $sql = 'UPDATE ' . TEAMPAGE_TABLE . ' + SET teampage_position = teampage_position - 1 + WHERE teampage_position > ' . $current_value; + $this->db->sql_query($sql); + + $sql = 'DELETE FROM ' . TEAMPAGE_TABLE . ' + WHERE group_id = ' . $group_id; + $this->db->sql_query($sql); + + $this->cache->destroy('sql', TEAMPAGE_TABLE); + return true; + } + + $this->cache->destroy('sql', TEAMPAGE_TABLE); + return false; + } + + /** + * Deletes an item from the list and closes the gap in the position list. + * + * @param int $teampage_id teampage_id of the item to be deleted + * @param bool $skip_group Skip setting the group to GROUP_DISABLED, to save the query, when you need to update it anyway. + * @return bool True if the item was deleted successfully + */ + public function delete_teampage($teampage_id, $skip_group = false) + { + $current_value = $this->get_teampage_value($teampage_id); + + if ($current_value != self::GROUP_DISABLED) + { + $sql = 'DELETE FROM ' . TEAMPAGE_TABLE . ' + WHERE teampage_id = ' . $teampage_id . ' + OR teampage_parent = ' . $teampage_id; + $this->db->sql_query($sql); + + $delta = (int) $this->db->sql_affectedrows(); + + $sql = 'UPDATE ' . TEAMPAGE_TABLE . ' + SET teampage_position = teampage_position - ' . $delta . ' + WHERE teampage_position > ' . $current_value; + $this->db->sql_query($sql); + + $this->cache->destroy('sql', TEAMPAGE_TABLE); + return true; + } + + $this->cache->destroy('sql', TEAMPAGE_TABLE); + return false; + } + + /** + * Moves a group up by group_id + * + * {@inheritDoc} + */ + public function move_up($group_id) + { + return $this->move($group_id, 1); + } + + /** + * Moves an item up by teampage_id + * + * @param int $group_id group_id of the group to be moved + * @return bool True if the group was moved successfully + */ + public function move_up_teampage($teampage_id) + { + return $this->move_teampage($teampage_id, 1); + } + + /** + * Moves a group down by group_id + * + * {@inheritDoc} + */ + public function move_down($group_id) + { + return $this->move($group_id, -1); + } + + /** + * Movesan item down by teampage_id + * + * @param int $group_id group_id of the group to be moved + * @return bool True if the group was moved successfully + */ + public function move_down_teampage($teampage_id) + { + return $this->move_teampage($teampage_id, -1); + } + + /** + * Moves a group up/down + * + * {@inheritDoc} + */ + public function move($group_id, $delta) + { + $delta = (int) $delta; + if (!$delta) + { + return false; + } + + $move_up = ($delta > 0) ? true : false; + $data = $this->get_group_values($group_id); + + $current_value = (int) $data['teampage_position']; + if ($current_value != self::GROUP_DISABLED) + { + $this->db->sql_transaction('begin'); + + if (!$move_up && $data['teampage_parent'] == self::NO_PARENT) + { + // If we move items down, we need to grab the one sibling more, + // so we do not ignore the children of the previous sibling. + // We will remove the additional sibling later on. + $delta = abs($delta) + 1; + } + + $sql = 'SELECT teampage_position + FROM ' . TEAMPAGE_TABLE . ' + WHERE teampage_parent = ' . (int) $data['teampage_parent'] . ' + AND teampage_position' . (($move_up) ? ' < ' : ' > ') . $current_value . ' + ORDER BY teampage_position' . (($move_up) ? ' DESC' : ' ASC'); + $result = $this->db->sql_query_limit($sql, $delta); + + $sibling_count = 0; + $sibling_limit = $delta; + + // Reset the delta, as we recalculate the new real delta + $delta = 0; + while ($row = $this->db->sql_fetchrow($result)) + { + $sibling_count++; + $delta = $current_value - $row['teampage_position']; + + if (!$move_up && $data['teampage_parent'] == self::NO_PARENT && $sibling_count == $sibling_limit) + { + // Remove the additional sibling we added previously + $delta++; + } + } + $this->db->sql_freeresult($result); + + if ($delta) + { + // First we move all items between our current value and the target value up/down 1, + // so we have a gap for our item to move. + $sql = 'UPDATE ' . TEAMPAGE_TABLE . ' + SET teampage_position = teampage_position' . (($move_up) ? ' + 1' : ' - 1') . ' + WHERE teampage_position' . (($move_up) ? ' >= ' : ' <= ') . ($current_value - $delta) . ' + AND teampage_position' . (($move_up) ? ' < ' : ' > ') . $current_value; + $this->db->sql_query($sql); + + // And now finally, when we moved some other items and built a gap, + // we can move the desired item to it. + $sql = 'UPDATE ' . TEAMPAGE_TABLE . ' + SET teampage_position = teampage_position ' . (($move_up) ? ' - ' : ' + ') . abs($delta) . ' + WHERE group_id = ' . (int) $group_id; + $this->db->sql_query($sql); + + $this->db->sql_transaction('commit'); + $this->cache->destroy('sql', TEAMPAGE_TABLE); + + return true; + } + + $this->db->sql_transaction('commit'); + } + + $this->cache->destroy('sql', TEAMPAGE_TABLE); + return false; + } + + /** + * Moves an item up/down + * + * @param int $teampage_id teampage_id of the item to be moved + * @param int $delta number of steps: + * - positive = move up + * - negative = move down + * @return bool True if the group was moved successfully + */ + public function move_teampage($teampage_id, $delta) + { + $delta = (int) $delta; + if (!$delta) + { + return false; + } + + $move_up = ($delta > 0) ? true : false; + $data = $this->get_teampage_values($teampage_id); + + $current_value = (int) $data['teampage_position']; + if ($current_value != self::GROUP_DISABLED) + { + $this->db->sql_transaction('begin'); + + if (!$move_up && $data['teampage_parent'] == self::NO_PARENT) + { + // If we move items down, we need to grab the one sibling more, + // so we do not ignore the children of the previous sibling. + // We will remove the additional sibling later on. + $delta = abs($delta) + 1; + } + + $sql = 'SELECT teampage_id, teampage_position + FROM ' . TEAMPAGE_TABLE . ' + WHERE teampage_parent = ' . (int) $data['teampage_parent'] . ' + AND teampage_position' . (($move_up) ? ' < ' : ' > ') . $current_value . ' + ORDER BY teampage_position' . (($move_up) ? ' DESC' : ' ASC'); + $result = $this->db->sql_query_limit($sql, $delta); + + $sibling_count = 0; + $sibling_limit = $delta; + + // Reset the delta, as we recalculate the new real delta + $delta = 0; + while ($row = $this->db->sql_fetchrow($result)) + { + $sibling_count++; + $delta = $current_value - $row['teampage_position']; + + // Remove the additional sibling we added previously + // But only, if we included it, this is not be the case + // when we reached the end of our list + if (!$move_up && $data['teampage_parent'] == self::NO_PARENT && $sibling_count == $sibling_limit) + { + $delta++; + } + } + $this->db->sql_freeresult($result); + + if ($delta) + { + $sql = 'SELECT COUNT(teampage_id) as num_items + FROM ' . TEAMPAGE_TABLE . ' + WHERE teampage_id = ' . (int) $teampage_id . ' + OR teampage_parent = ' . (int) $teampage_id; + $result = $this->db->sql_query($sql); + $num_items = (int) $this->db->sql_fetchfield('num_items'); + $this->db->sql_freeresult($result); + + // First we move all items between our current value and the target value up/down 1, + // so we have a gap for our item to move. + $sql = 'UPDATE ' . TEAMPAGE_TABLE . ' + SET teampage_position = teampage_position' . (($move_up) ? ' + ' : ' - ') . $num_items . ' + WHERE teampage_position' . (($move_up) ? ' >= ' : ' <= ') . ($current_value - $delta) . ' + AND teampage_position' . (($move_up) ? ' < ' : ' > ') . $current_value . ' + AND NOT (teampage_id = ' . (int) $teampage_id . ' + OR teampage_parent = ' . (int) $teampage_id . ')'; + $this->db->sql_query($sql); + + $delta = (!$move_up && $data['teampage_parent'] == self::NO_PARENT) ? (abs($delta) - ($num_items - 1)) : abs($delta); + + // And now finally, when we moved some other items and built a gap, + // we can move the desired item to it. + $sql = 'UPDATE ' . TEAMPAGE_TABLE . ' + SET teampage_position = teampage_position ' . (($move_up) ? ' - ' : ' + ') . $delta . ' + WHERE teampage_id = ' . (int) $teampage_id . ' + OR teampage_parent = ' . (int) $teampage_id; + $this->db->sql_query($sql); + + $this->db->sql_transaction('commit'); + $this->cache->destroy('sql', TEAMPAGE_TABLE); + + return true; + } + + $this->db->sql_transaction('commit'); + } + + $this->cache->destroy('sql', TEAMPAGE_TABLE); + return false; + } + + /** + * Get group type language var + * + * @param int $group_type group_type from the groups-table + * @return string name of the language variable for the given group-type. + */ + static public function group_type_language($group_type) + { + switch ($group_type) + { + case GROUP_OPEN: + return 'GROUP_REQUEST'; + case GROUP_CLOSED: + return 'GROUP_CLOSED'; + case GROUP_HIDDEN: + return 'GROUP_HIDDEN'; + case GROUP_SPECIAL: + return 'GROUP_SPECIAL'; + case GROUP_FREE: + return 'GROUP_OPEN'; + } + } +} diff --git a/phpBB/phpbb/hook/finder.php b/phpBB/phpbb/hook/finder.php new file mode 100644 index 0000000000..7b0412f733 --- /dev/null +++ b/phpBB/phpbb/hook/finder.php @@ -0,0 +1,84 @@ +phpbb_root_path = $phpbb_root_path; + $this->cache = $cache; + $this->php_ext = $php_ext; + } + + /** + * Finds all hook files. + * + * @param bool $cache Whether the result should be cached + * @return array An array of paths to found hook files + */ + public function find($cache = true) + { + if (!defined('DEBUG') && $cache && $this->cache) + { + $hook_files = $this->cache->get('_hooks'); + if ($hook_files !== false) + { + return $hook_files; + } + } + + $hook_files = array(); + + // Now search for hooks... + $dh = @opendir($this->phpbb_root_path . 'includes/hooks/'); + + if ($dh) + { + while (($file = readdir($dh)) !== false) + { + if (strpos($file, 'hook_') === 0 && substr($file, -strlen('.' . $this->php_ext)) === '.' . $this->php_ext) + { + $hook_files[] = substr($file, 0, -(strlen($this->php_ext) + 1)); + } + } + closedir($dh); + } + + if ($cache && $this->cache) + { + $this->cache->put('_hooks', $hook_files); + } + + return $hook_files; + } +} diff --git a/phpBB/phpbb/json_response.php b/phpBB/phpbb/json_response.php new file mode 100644 index 0000000000..5dd904da09 --- /dev/null +++ b/phpBB/phpbb/json_response.php @@ -0,0 +1,41 @@ +config_name = $config_name; + $this->config = $config; + $this->db = $db; + } + + /** + * Tries to acquire the lock by updating + * the configuration variable in the database. + * + * As a lock may only be held by one process at a time, lock + * acquisition may fail if another process is holding the lock + * or if another process obtained the lock but never released it. + * Locks are forcibly released after a timeout of 1 hour. + * + * @return bool true if lock was acquired + * false otherwise + */ + public function acquire() + { + if ($this->locked) + { + return false; + } + + if (!isset($this->config[$this->config_name])) + { + $this->config->set($this->config_name, '0', false); + } + $lock_value = $this->config[$this->config_name]; + + // make sure lock cannot be acquired by multiple processes + if ($lock_value) + { + // if the other process is running more than an hour already we have to assume it + // aborted without cleaning the lock + $time = explode(' ', $lock_value); + $time = $time[0]; + + if ($time + 3600 >= time()) + { + return false; + } + } + + $this->unique_id = time() . ' ' . unique_id(); + + // try to update the config value, if it was already modified by another + // process we failed to acquire the lock. + $this->locked = $this->config->set_atomic($this->config_name, $lock_value, $this->unique_id, false); + + return $this->locked; + } + + /** + * Does this process own the lock? + * + * @return bool true if lock is owned + * false otherwise + */ + public function owns_lock() + { + return (bool) $this->locked; + } + + /** + * Releases the lock. + * + * The lock must have been previously obtained, that is, acquire() call + * was issued and returned true. + * + * Note: Attempting to release a lock that is already released, + * that is, calling release() multiple times, is harmless. + * + * @return null + */ + public function release() + { + if ($this->locked) + { + $this->config->set_atomic($this->config_name, $this->unique_id, '0', false); + $this->locked = false; + } + } +} diff --git a/phpBB/phpbb/lock/flock.php b/phpBB/phpbb/lock/flock.php new file mode 100644 index 0000000000..17de0847c0 --- /dev/null +++ b/phpBB/phpbb/lock/flock.php @@ -0,0 +1,144 @@ +path = $path; + $this->lock_fp = null; + } + + /** + * Tries to acquire the lock. + * + * If the lock is already held by another process, this call will block + * until the other process releases the lock. If a lock is acquired and + * is not released before script finishes but the process continues to + * live (apache/fastcgi) then subsequent processes trying to acquire + * the same lock will be blocked forever. + * + * If the lock is already held by the same process via another instance + * of this class, this call will block forever. + * + * If flock function is disabled in php or fails to work, lock + * acquisition will fail and false will be returned. + * + * @return bool true if lock was acquired + * false otherwise + */ + public function acquire() + { + if ($this->lock_fp) + { + return false; + } + + // For systems that can't have two processes opening + // one file for writing simultaneously + if (file_exists($this->path . '.lock')) + { + $mode = 'rb'; + } + else + { + $mode = 'wb'; + } + + $this->lock_fp = @fopen($this->path . '.lock', $mode); + + if ($mode == 'wb') + { + if (!$this->lock_fp) + { + // Two processes may attempt to create lock file at the same time. + // Have the losing process try opening the lock file again for reading + // on the assumption that the winning process created it + $mode = 'rb'; + $this->lock_fp = @fopen($this->path . '.lock', $mode); + } + else + { + // Only need to set mode when the lock file is written + @chmod($this->path . '.lock', 0666); + } + } + + if ($this->lock_fp) + { + @flock($this->lock_fp, LOCK_EX); + } + + return (bool) $this->lock_fp; + } + + /** + * Does this process own the lock? + * + * @return bool true if lock is owned + * false otherwise + */ + public function owns_lock() + { + return (bool) $this->lock_fp; + } + + /** + * Releases the lock. + * + * The lock must have been previously obtained, that is, acquire() call + * was issued and returned true. + * + * Note: Attempting to release a lock that is already released, + * that is, calling release() multiple times, is harmless. + * + * @return null + */ + public function release() + { + if ($this->lock_fp) + { + @flock($this->lock_fp, LOCK_UN); + fclose($this->lock_fp); + $this->lock_fp = null; + } + } +} diff --git a/phpBB/phpbb/log/interface.php b/phpBB/phpbb/log/interface.php new file mode 100644 index 0000000000..3b459c9bdf --- /dev/null +++ b/phpBB/phpbb/log/interface.php @@ -0,0 +1,106 @@ +db = $db; + $this->user = $user; + $this->auth = $auth; + $this->dispatcher = $phpbb_dispatcher; + $this->phpbb_root_path = $phpbb_root_path; + $this->phpbb_admin_path = $this->phpbb_root_path . $relative_admin_path; + $this->php_ext = $php_ext; + $this->log_table = $log_table; + + /* + * IN_ADMIN is set after the session is created, + * so we need to take ADMIN_START into account as well, otherwise + * it will not work for the phpbb_log object we create in common.php + */ + $this->set_is_admin((defined('ADMIN_START') && ADMIN_START) || (defined('IN_ADMIN') && IN_ADMIN)); + $this->enable(); + } + + /** + * Set is_in_admin in order to return administrative user profile links + * in get_logs() + * + * @param bool $is_in_admin Are we called from within the acp? + * @return null + */ + public function set_is_admin($is_in_admin) + { + $this->is_in_admin = (bool) $is_in_admin; + } + + /** + * Returns the is_in_admin option + * + * @return bool + */ + public function get_is_admin() + { + return $this->is_in_admin; + } + + /** + * Set table name + * + * @param string $log_table Can overwrite the table to use for the logs + * @return null + */ + public function set_log_table($log_table) + { + $this->log_table = $log_table; + } + + /** + * This function returns the state of the log system. + * + * {@inheritDoc} + */ + public function is_enabled($type = '') + { + if ($type == '' || $type == 'all') + { + return !isset($this->disabled_types['all']); + } + return !isset($this->disabled_types[$type]) && !isset($this->disabled_types['all']); + } + + /** + * Disable log + * + * This function allows disabling the log system or parts of it, for this + * page call. When add_log is called and the type is disabled, + * the log will not be added to the database. + * + * {@inheritDoc} + */ + public function disable($type = '') + { + if (is_array($type)) + { + foreach ($type as $disable_type) + { + $this->disable($disable_type); + } + return; + } + + // Empty string is an equivalent for all types. + if ($type == '') + { + $type = 'all'; + } + $this->disabled_types[$type] = true; + } + + /** + * Enable log + * + * This function allows re-enabling the log system. + * + * {@inheritDoc} + */ + public function enable($type = '') + { + if (is_array($type)) + { + foreach ($type as $enable_type) + { + $this->enable($enable_type); + } + return; + } + + if ($type == '' || $type == 'all') + { + $this->disabled_types = array(); + return; + } + unset($this->disabled_types[$type]); + } + + /** + * Adds a log to the database + * + * {@inheritDoc} + */ + public function add($mode, $user_id, $log_ip, $log_operation, $log_time = false, $additional_data = array()) + { + if (!$this->is_enabled($mode)) + { + return false; + } + + if ($log_time == false) + { + $log_time = time(); + } + + $sql_ary = array( + 'user_id' => $user_id, + 'log_ip' => $log_ip, + 'log_time' => $log_time, + 'log_operation' => $log_operation, + ); + + switch ($mode) + { + case 'admin': + $sql_ary += array( + 'log_type' => LOG_ADMIN, + 'log_data' => (!empty($additional_data)) ? serialize($additional_data) : '', + ); + break; + + case 'mod': + $forum_id = (int) $additional_data['forum_id']; + unset($additional_data['forum_id']); + $topic_id = (int) $additional_data['topic_id']; + unset($additional_data['topic_id']); + $sql_ary += array( + 'log_type' => LOG_MOD, + 'forum_id' => $forum_id, + 'topic_id' => $topic_id, + 'log_data' => (!empty($additional_data)) ? serialize($additional_data) : '', + ); + break; + + case 'user': + $reportee_id = (int) $additional_data['reportee_id']; + unset($additional_data['reportee_id']); + + $sql_ary += array( + 'log_type' => LOG_USERS, + 'reportee_id' => $reportee_id, + 'log_data' => (!empty($additional_data)) ? serialize($additional_data) : '', + ); + break; + + case 'critical': + $sql_ary += array( + 'log_type' => LOG_CRITICAL, + 'log_data' => (!empty($additional_data)) ? serialize($additional_data) : '', + ); + break; + } + + /** + * Allows to modify log data before we add it to the database + * + * NOTE: if sql_ary does not contain a log_type value, the entry will + * not be stored in the database. So ensure to set it, if needed. + * + * @event core.add_log + * @var string mode Mode of the entry we log + * @var int user_id ID of the user who triggered the log + * @var string log_ip IP of the user who triggered the log + * @var string log_operation Language key of the log operation + * @var int log_time Timestamp, when the log was added + * @var array additional_data Array with additional log data + * @var array sql_ary Array with log data we insert into the + * database. If sql_ary[log_type] is not set, + * we won't add the entry to the database. + * @since 3.1-A1 + */ + $vars = array('mode', 'user_id', 'log_ip', 'log_operation', 'log_time', 'additional_data', 'sql_ary'); + extract($this->dispatcher->trigger_event('core.add_log', $vars)); + + // We didn't find a log_type, so we don't save it in the database. + if (!isset($sql_ary['log_type'])) + { + return false; + } + + $this->db->sql_query('INSERT INTO ' . $this->log_table . ' ' . $this->db->sql_build_array('INSERT', $sql_ary)); + + return $this->db->sql_nextid(); + } + + /** + * Grab the logs from the database + * + * {@inheritDoc} + */ + public function get_logs($mode, $count_logs = true, $limit = 0, $offset = 0, $forum_id = 0, $topic_id = 0, $user_id = 0, $log_time = 0, $sort_by = 'l.log_time DESC', $keywords = '') + { + $this->entry_count = 0; + $this->last_page_offset = $offset; + + $topic_id_list = $reportee_id_list = array(); + + $profile_url = ($this->get_is_admin() && $this->phpbb_admin_path) ? append_sid("{$this->phpbb_admin_path}index.{$this->php_ext}", 'i=users&mode=overview') : append_sid("{$this->phpbb_root_path}memberlist.{$this->php_ext}", 'mode=viewprofile'); + + switch ($mode) + { + case 'admin': + $log_type = LOG_ADMIN; + $sql_additional = ''; + break; + + case 'mod': + $log_type = LOG_MOD; + $sql_additional = ''; + + if ($topic_id) + { + $sql_additional = 'AND l.topic_id = ' . (int) $topic_id; + } + else if (is_array($forum_id)) + { + $sql_additional = 'AND ' . $this->db->sql_in_set('l.forum_id', array_map('intval', $forum_id)); + } + else if ($forum_id) + { + $sql_additional = 'AND l.forum_id = ' . (int) $forum_id; + } + break; + + case 'user': + $log_type = LOG_USERS; + $sql_additional = 'AND l.reportee_id = ' . (int) $user_id; + break; + + case 'users': + $log_type = LOG_USERS; + $sql_additional = ''; + break; + + case 'critical': + $log_type = LOG_CRITICAL; + $sql_additional = ''; + break; + + default: + $log_type = false; + $sql_additional = ''; + } + + /** + * Overwrite log type and limitations before we count and get the logs + * + * NOTE: if log_type is false, no entries will be returned. + * + * @event core.get_logs_modify_type + * @var string mode Mode of the entries we display + * @var bool count_logs Do we count all matching entries? + * @var int limit Limit the number of entries + * @var int offset Offset when fetching the entries + * @var mixed forum_id Limit entries to the forum_id, + * can also be an array of forum_ids + * @var int topic_id Limit entries to the topic_id + * @var int user_id Limit entries to the user_id + * @var int log_time Limit maximum age of log entries + * @var string sort_by SQL order option + * @var string keywords Will only return entries that have the + * keywords in log_operation or log_data + * @var string profile_url URL to the users profile + * @var int log_type Limit logs to a certain type. If log_type + * is false, no entries will be returned. + * @var string sql_additional Additional conditions for the entries, + * e.g.: 'AND l.forum_id = 1' + * @since 3.1-A1 + */ + $vars = array('mode', 'count_logs', 'limit', 'offset', 'forum_id', 'topic_id', 'user_id', 'log_time', 'sort_by', 'keywords', 'profile_url', 'log_type', 'sql_additional'); + extract($this->dispatcher->trigger_event('core.get_logs_modify_type', $vars)); + + if ($log_type === false) + { + $this->last_page_offset = 0; + return array(); + } + + $sql_keywords = ''; + if (!empty($keywords)) + { + // Get the SQL condition for our keywords + $sql_keywords = $this->generate_sql_keyword($keywords); + } + + if ($count_logs) + { + $sql = 'SELECT COUNT(l.log_id) AS total_entries + FROM ' . LOG_TABLE . ' l, ' . USERS_TABLE . ' u + WHERE l.log_type = ' . (int) $log_type . ' + AND l.user_id = u.user_id + AND l.log_time >= ' . (int) $log_time . " + $sql_keywords + $sql_additional"; + $result = $this->db->sql_query($sql); + $this->entry_count = (int) $this->db->sql_fetchfield('total_entries'); + $this->db->sql_freeresult($result); + + if ($this->entry_count == 0) + { + // Save the queries, because there are no logs to display + $this->last_page_offset = 0; + return array(); + } + + // Return the user to the last page that is valid + while ($this->last_page_offset >= $this->entry_count) + { + $this->last_page_offset = max(0, $this->last_page_offset - $limit); + } + } + + $sql = 'SELECT l.*, u.username, u.username_clean, u.user_colour + FROM ' . LOG_TABLE . ' l, ' . USERS_TABLE . ' u + WHERE l.log_type = ' . (int) $log_type . ' + AND u.user_id = l.user_id + ' . (($log_time) ? 'AND l.log_time >= ' . (int) $log_time : '') . " + $sql_keywords + $sql_additional + ORDER BY $sort_by"; + $result = $this->db->sql_query_limit($sql, $limit, $this->last_page_offset); + + $i = 0; + $log = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $row['forum_id'] = (int) $row['forum_id']; + if ($row['topic_id']) + { + $topic_id_list[] = (int) $row['topic_id']; + } + + if ($row['reportee_id']) + { + $reportee_id_list[] = (int) $row['reportee_id']; + } + + $log_entry_data = array( + 'id' => (int) $row['log_id'], + + 'reportee_id' => (int) $row['reportee_id'], + 'reportee_username' => '', + 'reportee_username_full'=> '', + + 'user_id' => (int) $row['user_id'], + 'username' => $row['username'], + 'username_full' => get_username_string('full', $row['user_id'], $row['username'], $row['user_colour'], false, $profile_url), + + 'ip' => $row['log_ip'], + 'time' => (int) $row['log_time'], + 'forum_id' => (int) $row['forum_id'], + 'topic_id' => (int) $row['topic_id'], + + 'viewforum' => ($row['forum_id'] && $this->auth->acl_get('f_read', $row['forum_id'])) ? append_sid("{$this->phpbb_root_path}viewforum.{$this->php_ext}", 'f=' . $row['forum_id']) : false, + 'action' => (isset($this->user->lang[$row['log_operation']])) ? $this->user->lang[$row['log_operation']] : '{' . ucfirst(str_replace('_', ' ', $row['log_operation'])) . '}', + ); + + /** + * Modify the entry's data before it is returned + * + * @event core.get_logs_modify_entry_data + * @var array row Entry data from the database + * @var array log_entry_data Entry's data which is returned + * @since 3.1-A1 + */ + $vars = array('row', 'log_entry_data'); + extract($this->dispatcher->trigger_event('core.get_logs_modify_entry_data', $vars)); + + $log[$i] = $log_entry_data; + + if (!empty($row['log_data'])) + { + $log_data_ary = unserialize($row['log_data']); + $log_data_ary = ($log_data_ary !== false) ? $log_data_ary : array(); + + if (isset($this->user->lang[$row['log_operation']])) + { + // Check if there are more occurrences of % than + // arguments, if there are we fill out the arguments + // array. It doesn't matter if we add more arguments than + // placeholders. + if ((substr_count($log[$i]['action'], '%') - sizeof($log_data_ary)) > 0) + { + $log_data_ary = array_merge($log_data_ary, array_fill(0, substr_count($log[$i]['action'], '%') - sizeof($log_data_ary), '')); + } + + $log[$i]['action'] = vsprintf($log[$i]['action'], $log_data_ary); + + // If within the admin panel we do not censor text out + if ($this->get_is_admin()) + { + $log[$i]['action'] = bbcode_nl2br($log[$i]['action']); + } + else + { + $log[$i]['action'] = bbcode_nl2br(censor_text($log[$i]['action'])); + } + } + else if (!empty($log_data_ary)) + { + $log[$i]['action'] .= '
' . implode('', $log_data_ary); + } + + /* Apply make_clickable... has to be seen if it is for good. :/ + // Seems to be not for the moment, reconsider later... + $log[$i]['action'] = make_clickable($log[$i]['action']); + */ + } + + $i++; + } + $this->db->sql_freeresult($result); + + /** + * Get some additional data after we got all log entries + * + * @event core.get_logs_get_additional_data + * @var array log Array with all our log entries + * @var array topic_id_list Array of topic ids, for which we + * get the permission data + * @var array reportee_id_list Array of additional user IDs we + * get the username strings for + * @since 3.1-A1 + */ + $vars = array('log', 'topic_id_list', 'reportee_id_list'); + extract($this->dispatcher->trigger_event('core.get_logs_get_additional_data', $vars)); + + if (sizeof($topic_id_list)) + { + $topic_auth = $this->get_topic_auth($topic_id_list); + + foreach ($log as $key => $row) + { + $log[$key]['viewtopic'] = (isset($topic_auth['f_read'][$row['topic_id']])) ? append_sid("{$this->phpbb_root_path}viewtopic.{$this->php_ext}", 'f=' . $topic_auth['f_read'][$row['topic_id']] . '&t=' . $row['topic_id']) : false; + $log[$key]['viewlogs'] = (isset($topic_auth['m_'][$row['topic_id']])) ? append_sid("{$this->phpbb_root_path}mcp.{$this->php_ext}", 'i=logs&mode=topic_logs&t=' . $row['topic_id'], true, $this->user->session_id) : false; + } + } + + if (sizeof($reportee_id_list)) + { + $reportee_data_list = $this->get_reportee_data($reportee_id_list); + + foreach ($log as $key => $row) + { + if (!isset($reportee_data_list[$row['reportee_id']])) + { + continue; + } + + $log[$key]['reportee_username'] = $reportee_data_list[$row['reportee_id']]['username']; + $log[$key]['reportee_username_full'] = get_username_string('full', $row['reportee_id'], $reportee_data_list[$row['reportee_id']]['username'], $reportee_data_list[$row['reportee_id']]['user_colour'], false, $profile_url); + } + } + + return $log; + } + + /** + * Generates a sql condition for the specified keywords + * + * @param string $keywords The keywords the user specified to search for + * + * @return string Returns the SQL condition searching for the keywords + */ + protected function generate_sql_keyword($keywords) + { + // Use no preg_quote for $keywords because this would lead to sole + // backslashes being added. We also use an OR connection here for + // spaces and the | string. Currently, regex is not supported for + // searching (but may come later). + $keywords = preg_split('#[\s|]+#u', utf8_strtolower($keywords), 0, PREG_SPLIT_NO_EMPTY); + $sql_keywords = ''; + + if (!empty($keywords)) + { + $keywords_pattern = array(); + + // Build pattern and keywords... + for ($i = 0, $num_keywords = sizeof($keywords); $i < $num_keywords; $i++) + { + $keywords_pattern[] = preg_quote($keywords[$i], '#'); + $keywords[$i] = $this->db->sql_like_expression($this->db->any_char . $keywords[$i] . $this->db->any_char); + } + + $keywords_pattern = '#' . implode('|', $keywords_pattern) . '#ui'; + + $operations = array(); + foreach ($this->user->lang as $key => $value) + { + if (substr($key, 0, 4) == 'LOG_' && preg_match($keywords_pattern, $value)) + { + $operations[] = $key; + } + } + + $sql_keywords = 'AND ('; + if (!empty($operations)) + { + $sql_keywords .= $this->db->sql_in_set('l.log_operation', $operations) . ' OR '; + } + $sql_lower = $this->db->sql_lower_text('l.log_data'); + $sql_keywords .= " $sql_lower " . implode(" OR $sql_lower ", $keywords) . ')'; + } + + return $sql_keywords; + } + + /** + * Determine whether the user is allowed to read and/or moderate the forum of the topic + * + * @param array $topic_ids Array with the topic ids + * + * @return array Returns an array with two keys 'm_' and 'read_f' which are also an array of topic_id => forum_id sets when the permissions are given. Sample: + * array( + * 'permission' => array( + * topic_id => forum_id + * ), + * ), + */ + protected function get_topic_auth(array $topic_ids) + { + $forum_auth = array('f_read' => array(), 'm_' => array()); + $topic_ids = array_unique($topic_ids); + + $sql = 'SELECT topic_id, forum_id + FROM ' . TOPICS_TABLE . ' + WHERE ' . $this->db->sql_in_set('topic_id', array_map('intval', $topic_ids)); + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $row['topic_id'] = (int) $row['topic_id']; + $row['forum_id'] = (int) $row['forum_id']; + + if ($this->auth->acl_get('f_read', $row['forum_id'])) + { + $forum_auth['f_read'][$row['topic_id']] = $row['forum_id']; + } + + if ($this->auth->acl_gets('a_', 'm_', $row['forum_id'])) + { + $forum_auth['m_'][$row['topic_id']] = $row['forum_id']; + } + } + $this->db->sql_freeresult($result); + + return $forum_auth; + } + + /** + * Get the data for all reportee from the database + * + * @param array $reportee_ids Array with the user ids of the reportees + * + * @return array Returns an array with the reportee data + */ + protected function get_reportee_data(array $reportee_ids) + { + $reportee_ids = array_unique($reportee_ids); + $reportee_data_list = array(); + + $sql = 'SELECT user_id, username, user_colour + FROM ' . USERS_TABLE . ' + WHERE ' . $this->db->sql_in_set('user_id', $reportee_ids); + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $reportee_data_list[$row['user_id']] = $row; + } + $this->db->sql_freeresult($result); + + return $reportee_data_list; + } + + /** + * Get total log count + * + * {@inheritDoc} + */ + public function get_log_count() + { + return ($this->entry_count) ? $this->entry_count : 0; + } + + /** + * Get offset of the last valid log page + * + * {@inheritDoc} + */ + public function get_valid_offset() + { + return ($this->last_page_offset) ? $this->last_page_offset : 0; + } +} diff --git a/phpBB/phpbb/notification/exception.php b/phpBB/phpbb/notification/exception.php new file mode 100644 index 0000000000..a52d6fdc57 --- /dev/null +++ b/phpBB/phpbb/notification/exception.php @@ -0,0 +1,29 @@ +getMessage(); + } +} diff --git a/phpBB/phpbb/notification/manager.php b/phpBB/phpbb/notification/manager.php new file mode 100644 index 0000000000..97833710c0 --- /dev/null +++ b/phpBB/phpbb/notification/manager.php @@ -0,0 +1,910 @@ +notification_types = $notification_types; + $this->notification_methods = $notification_methods; + $this->phpbb_container = $phpbb_container; + + $this->user_loader = $user_loader; + $this->db = $db; + $this->cache = $cache; + $this->user = $user; + + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + + $this->notification_types_table = $notification_types_table; + $this->notifications_table = $notifications_table; + $this->user_notifications_table = $user_notifications_table; + } + + /** + * Load the user's notifications + * + * @param array $options Optional options to control what notifications are loaded + * notification_id Notification id to load (or array of notification ids) + * user_id User id to load notifications for (Default: $user->data['user_id']) + * order_by Order by (Default: notification_time) + * order_dir Order direction (Default: DESC) + * limit Number of notifications to load (Default: 5) + * start Notifications offset (Default: 0) + * all_unread Load all unread notifications? If set to true, count_unread is set to true (Default: false) + * count_unread Count all unread notifications? (Default: false) + * count_total Count all notifications? (Default: false) + * @return array Array of information based on the request with keys: + * 'notifications' array of notification type objects + * 'unread_count' number of unread notifications the user has if count_unread is true in the options + * 'total_count' number of notifications the user has if count_total is true in the options + */ + public function load_notifications(array $options = array()) + { + // Merge default options + $options = array_merge(array( + 'notification_id' => false, + 'user_id' => $this->user->data['user_id'], + 'order_by' => 'notification_time', + 'order_dir' => 'DESC', + 'limit' => 0, + 'start' => 0, + 'all_unread' => false, + 'count_unread' => false, + 'count_total' => false, + ), $options); + + // If all_unread, count_unread must be true + $options['count_unread'] = ($options['all_unread']) ? true : $options['count_unread']; + + // Anonymous users and bots never receive notifications + if ($options['user_id'] == $this->user->data['user_id'] && ($this->user->data['user_id'] == ANONYMOUS || $this->user->data['user_type'] == USER_IGNORE)) + { + return array( + 'notifications' => array(), + 'unread_count' => 0, + 'total_count' => 0, + ); + } + + $notifications = $user_ids = array(); + $load_special = array(); + $total_count = $unread_count = 0; + + if ($options['count_unread']) + { + // Get the total number of unread notifications + $sql = 'SELECT COUNT(n.notification_id) AS unread_count + FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . ' nt + WHERE n.user_id = ' . (int) $options['user_id'] . ' + AND n.notification_read = 0 + AND nt.notification_type_id = n.notification_type_id + AND nt.notification_type_enabled = 1'; + $result = $this->db->sql_query($sql); + $unread_count = (int) $this->db->sql_fetchfield('unread_count', $result); + $this->db->sql_freeresult($result); + } + + if ($options['count_total']) + { + // Get the total number of notifications + $sql = 'SELECT COUNT(n.notification_id) AS total_count + FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . ' nt + WHERE n.user_id = ' . (int) $options['user_id'] . ' + AND nt.notification_type_id = n.notification_type_id + AND nt.notification_type_enabled = 1'; + $result = $this->db->sql_query($sql); + $total_count = (int) $this->db->sql_fetchfield('total_count', $result); + $this->db->sql_freeresult($result); + } + + if (!$options['count_total'] || $total_count) + { + $rowset = array(); + + // Get the main notifications + $sql = 'SELECT n.*, nt.notification_type_name + FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . ' nt + WHERE n.user_id = ' . (int) $options['user_id'] . + (($options['notification_id']) ? ((is_array($options['notification_id'])) ? ' AND ' . $this->db->sql_in_set('n.notification_id', $options['notification_id']) : ' AND n.notification_id = ' . (int) $options['notification_id']) : '') . ' + AND nt.notification_type_id = n.notification_type_id + AND nt.notification_type_enabled = 1 + ORDER BY n.' . $this->db->sql_escape($options['order_by']) . ' ' . $this->db->sql_escape($options['order_dir']); + $result = $this->db->sql_query_limit($sql, $options['limit'], $options['start']); + + while ($row = $this->db->sql_fetchrow($result)) + { + $rowset[$row['notification_id']] = $row; + } + $this->db->sql_freeresult($result); + + // Get all unread notifications + if ($unread_count && $options['all_unread'] && !empty($rowset)) + { + $sql = 'SELECT n.*, nt.notification_type_name + FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . ' nt + WHERE n.user_id = ' . (int) $options['user_id'] . ' + AND n.notification_read = 0 + AND ' . $this->db->sql_in_set('n.notification_id', array_keys($rowset), true) . ' + AND nt.notification_type_id = n.notification_type_id + AND nt.notification_type_enabled = 1 + ORDER BY n.' . $this->db->sql_escape($options['order_by']) . ' ' . $this->db->sql_escape($options['order_dir']); + $result = $this->db->sql_query_limit($sql, $options['limit'], $options['start']); + + while ($row = $this->db->sql_fetchrow($result)) + { + $rowset[$row['notification_id']] = $row; + } + $this->db->sql_freeresult($result); + } + + foreach ($rowset as $row) + { + $notification = $this->get_item_type_class($row['notification_type_name'], $row); + + // Array of user_ids to query all at once + $user_ids = array_merge($user_ids, $notification->users_to_query()); + + // Some notification types also require querying additional tables themselves + if (!isset($load_special[$row['notification_type_name']])) + { + $load_special[$row['notification_type_name']] = array(); + } + $load_special[$row['notification_type_name']] = array_merge($load_special[$row['notification_type_name']], $notification->get_load_special()); + + $notifications[$row['notification_id']] = $notification; + } + + $this->user_loader->load_users($user_ids); + + // Allow each type to load its own special items + foreach ($load_special as $item_type => $data) + { + $item_class = $this->get_item_type_class($item_type); + + $item_class->load_special($data, $notifications); + } + } + + return array( + 'notifications' => $notifications, + 'unread_count' => $unread_count, + 'total_count' => $total_count, + ); + } + + /** + * Mark notifications read + * + * @param bool|string|array $notification_type_name Type identifier or array of item types (only acceptable if the $data is identical for the specified types). False to mark read for all item types + * @param bool|int|array $item_id Item id or array of item ids. False to mark read for all item ids + * @param bool|int|array $user_id User id or array of user ids. False to mark read for all user ids + * @param bool|int $time Time at which to mark all notifications prior to as read. False to mark all as read. (Default: False) + */ + public function mark_notifications_read($notification_type_name, $item_id, $user_id, $time = false) + { + $time = ($time !== false) ? $time : time(); + + $sql = 'UPDATE ' . $this->notifications_table . " + SET notification_read = 1 + WHERE notification_time <= " . (int) $time . + (($notification_type_name !== false) ? ' AND ' . + (is_array($notification_type_name) ? $this->db->sql_in_set('notification_type_id', $this->get_notification_type_ids($notification_type_name)) : 'notification_type_id = ' . $this->get_notification_type_id($notification_type_name)) + : '') . + (($user_id !== false) ? ' AND ' . (is_array($user_id) ? $this->db->sql_in_set('user_id', $user_id) : 'user_id = ' . (int) $user_id) : '') . + (($item_id !== false) ? ' AND ' . (is_array($item_id) ? $this->db->sql_in_set('item_id', $item_id) : 'item_id = ' . (int) $item_id) : ''); + $this->db->sql_query($sql); + } + + /** + * Mark notifications read from a parent identifier + * + * @param string|array $notification_type_name Type identifier or array of item types (only acceptable if the $data is identical for the specified types) + * @param bool|int|array $item_parent_id Item parent id or array of item parent ids. False to mark read for all item parent ids + * @param bool|int|array $user_id User id or array of user ids. False to mark read for all user ids + * @param bool|int $time Time at which to mark all notifications prior to as read. False to mark all as read. (Default: False) + */ + public function mark_notifications_read_by_parent($notification_type_name, $item_parent_id, $user_id, $time = false) + { + $time = ($time !== false) ? $time : time(); + + $sql = 'UPDATE ' . $this->notifications_table . " + SET notification_read = 1 + WHERE notification_time <= " . (int) $time . + (($notification_type_name !== false) ? ' AND ' . + (is_array($notification_type_name) ? $this->db->sql_in_set('notification_type_id', $this->get_notification_type_ids($notification_type_name)) : 'notification_type_id = ' . $this->get_notification_type_id($notification_type_name)) + : '') . + (($item_parent_id !== false) ? ' AND ' . (is_array($item_parent_id) ? $this->db->sql_in_set('item_parent_id', $item_parent_id) : 'item_parent_id = ' . (int) $item_parent_id) : '') . + (($user_id !== false) ? ' AND ' . (is_array($user_id) ? $this->db->sql_in_set('user_id', $user_id) : 'user_id = ' . (int) $user_id) : ''); + $this->db->sql_query($sql); + } + + /** + * Mark notifications read + * + * @param int|array $notification_id Notification id or array of notification ids. + * @param bool|int $time Time at which to mark all notifications prior to as read. False to mark all as read. (Default: False) + */ + public function mark_notifications_read_by_id($notification_id, $time = false) + { + $time = ($time !== false) ? $time : time(); + + $sql = 'UPDATE ' . $this->notifications_table . " + SET notification_read = 1 + WHERE notification_time <= " . (int) $time . ' + AND ' . ((is_array($notification_id)) ? $this->db->sql_in_set('notification_id', $notification_id) : 'notification_id = ' . (int) $notification_id); + $this->db->sql_query($sql); + } + + /** + * Add a notification + * + * @param string|array $notification_type_name Type identifier or array of item types (only acceptable if the $data is identical for the specified types) + * Note: If you send an array of types, any user who could receive multiple notifications from this single item will only receive + * a single notification. If they MUST receive multiple notifications, call this function multiple times instead of sending an array + * @param array $data Data specific for this type that will be inserted + * @param array $options Optional options to control what notifications are loaded + * ignore_users array of data to specify which users should not receive certain types of notifications + * @return array Information about what users were notified and how they were notified + */ + public function add_notifications($notification_type_name, $data, array $options = array()) + { + $options = array_merge(array( + 'ignore_users' => array(), + ), $options); + + if (is_array($notification_type_name)) + { + $notified_users = array(); + $temp_options = $options; + + foreach ($notification_type_name as $type) + { + $temp_options['ignore_users'] = $options['ignore_users'] + $notified_users; + $notified_users += $this->add_notifications($type, $data, $temp_options); + } + + return $notified_users; + } + + $item_id = $this->get_item_type_class($notification_type_name)->get_item_id($data); + + // find out which users want to receive this type of notification + $notify_users = $this->get_item_type_class($notification_type_name)->find_users_for_notification($data, $options); + + $this->add_notifications_for_users($notification_type_name, $data, $notify_users); + + return $notify_users; + } + + /** + * Add a notification for specific users + * + * @param string|array $notification_type_name Type identifier or array of item types (only acceptable if the $data is identical for the specified types) + * @param array $data Data specific for this type that will be inserted + * @param array $notify_users User list to notify + */ + public function add_notifications_for_users($notification_type_name, $data, $notify_users) + { + if (is_array($notification_type_name)) + { + foreach ($notification_type_name as $type) + { + $this->add_notifications_for_users($type, $data, $notify_users); + } + + return; + } + + $notification_type_id = $this->get_notification_type_id($notification_type_name); + + $item_id = $this->get_item_type_class($notification_type_name)->get_item_id($data); + + $user_ids = array(); + $notification_objects = $notification_methods = array(); + + // Never send notifications to the anonymous user! + unset($notify_users[ANONYMOUS]); + + // Make sure not to send new notifications to users who've already been notified about this item + // This may happen when an item was added, but now new users are able to see the item + $sql = 'SELECT n.user_id + FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . ' nt + WHERE n.notification_type_id = ' . (int) $notification_type_id . ' + AND n.item_id = ' . (int) $item_id . ' + AND nt.notification_type_id = n.notification_type_id + AND nt.notification_type_enabled = 1'; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + unset($notify_users[$row['user_id']]); + } + $this->db->sql_freeresult($result); + + if (!sizeof($notify_users)) + { + return; + } + + // Allow notifications to perform actions before creating the insert array (such as run a query to cache some data needed for all notifications) + $notification = $this->get_item_type_class($notification_type_name); + $pre_create_data = $notification->pre_create_insert_array($data, $notify_users); + unset($notification); + + $insert_buffer = new phpbb_db_sql_insert_buffer($this->db, $this->notifications_table); + + // Go through each user so we can insert a row in the DB and then notify them by their desired means + foreach ($notify_users as $user => $methods) + { + $notification = $this->get_item_type_class($notification_type_name); + + $notification->user_id = (int) $user; + + // Insert notification row using buffer. + $insert_buffer->insert($notification->create_insert_array($data, $pre_create_data)); + + // Users are needed to send notifications + $user_ids = array_merge($user_ids, $notification->users_to_query()); + + foreach ($methods as $method) + { + // setup the notification methods and add the notification to the queue + if ($method) // blank means we just insert it as a notification, but do not notify them by any other means + { + if (!isset($notification_methods[$method])) + { + $notification_methods[$method] = $this->get_method_class($method); + } + + $notification_methods[$method]->add_to_queue($notification); + } + } + } + + $insert_buffer->flush(); + + // We need to load all of the users to send notifications + $this->user_loader->load_users($user_ids); + + // run the queue for each method to send notifications + foreach ($notification_methods as $method) + { + $method->notify(); + } + } + + /** + * Update a notification + * + * @param string|array $notification_type_name Type identifier or array of item types (only acceptable if the $data is identical for the specified types) + * @param array $data Data specific for this type that will be updated + */ + public function update_notifications($notification_type_name, $data) + { + if (is_array($notification_type_name)) + { + foreach ($notification_type_name as $type) + { + $this->update_notifications($type, $data); + } + + return; + } + + $notification = $this->get_item_type_class($notification_type_name); + + // Allow the notifications class to over-ride the update_notifications functionality + if (method_exists($notification, 'update_notifications')) + { + // Return False to over-ride the rest of the update + if ($notification->update_notifications($data) === false) + { + return; + } + } + + $notification_type_id = $this->get_notification_type_id($notification_type_name); + $item_id = $notification->get_item_id($data); + $update_array = $notification->create_update_array($data); + + $sql = 'UPDATE ' . $this->notifications_table . ' + SET ' . $this->db->sql_build_array('UPDATE', $update_array) . ' + WHERE notification_type_id = ' . (int) $notification_type_id . ' + AND item_id = ' . (int) $item_id; + $this->db->sql_query($sql); + } + + /** + * Delete a notification + * + * @param string|array $notification_type_name Type identifier or array of item types (only acceptable if the $item_id is identical for the specified types) + * @param int|array $item_id Identifier within the type (or array of ids) + * @param array $data Data specific for this type that will be updated + */ + public function delete_notifications($notification_type_name, $item_id) + { + if (is_array($notification_type_name)) + { + foreach ($notification_type_name as $type) + { + $this->delete_notifications($type, $item_id); + } + + return; + } + + $notification_type_id = $this->get_notification_type_id($notification_type_name); + + $sql = 'DELETE FROM ' . $this->notifications_table . ' + WHERE notification_type_id = ' . (int) $notification_type_id . ' + AND ' . (is_array($item_id) ? $this->db->sql_in_set('item_id', $item_id) : 'item_id = ' . (int) $item_id); + $this->db->sql_query($sql); + } + + /** + * Get all of the subscription types + * + * @return array Array of item types + */ + public function get_subscription_types() + { + $subscription_types = array(); + + foreach ($this->notification_types as $type_name => $data) + { + $type = $this->get_item_type_class($type_name); + + if ($type instanceof phpbb_notification_type_interface && $type->is_available()) + { + $options = array_merge(array( + 'id' => $type->get_type(), + 'lang' => 'NOTIFICATION_TYPE_' . strtoupper($type->get_type()), + 'group' => 'NOTIFICATION_GROUP_MISCELLANEOUS', + ), (($type::$notification_option !== false) ? $type::$notification_option : array())); + + $subscription_types[$options['group']][$options['id']] = $options; + } + } + + // Move Miscellaneous to the very last section + if (isset($subscription_types['NOTIFICATION_GROUP_MISCELLANEOUS'])) + { + $miscellaneous = $subscription_types['NOTIFICATION_GROUP_MISCELLANEOUS']; + unset($subscription_types['NOTIFICATION_GROUP_MISCELLANEOUS']); + $subscription_types['NOTIFICATION_GROUP_MISCELLANEOUS'] = $miscellaneous; + } + + return $subscription_types; + } + + /** + * Get all of the subscription methods + * + * @return array Array of methods + */ + public function get_subscription_methods() + { + $subscription_methods = array(); + + foreach ($this->notification_methods as $method_name => $data) + { + $method = $this->get_method_class($method_name); + + if ($method instanceof phpbb_notification_method_interface && $method->is_available()) + { + $subscription_methods[$method_name] = array( + 'id' => $method->get_type(), + 'lang' => 'NOTIFICATION_METHOD_' . strtoupper($method->get_type()), + ); + } + } + + return $subscription_methods; + } + + /** + * Get global subscriptions (item_id = 0) + * + * @param bool|int $user_id The user_id to add the subscription for (bool false for current user) + * + * @return array Subscriptions + */ + public function get_global_subscriptions($user_id = false) + { + $user_id = ($user_id === false) ? $this->user->data['user_id'] : $user_id; + + $subscriptions = array(); + + foreach ($this->get_subscription_types() as $group_name => $types) + { + foreach ($types as $id => $type) + { + $sql = 'SELECT method, notify + FROM ' . $this->user_notifications_table . ' + WHERE user_id = ' . (int) $user_id . " + AND item_type = '" . $this->db->sql_escape($id) . "' + AND item_id = 0"; + $result = $this->db->sql_query($sql); + + $row = $this->db->sql_fetchrow($result); + if (!$row) + { + // No rows at all, default to '' + $subscriptions[$id] = array(''); + } + else + { + do + { + if (!$row['notify']) + { + continue; + } + + if (!isset($subscriptions[$id])) + { + $subscriptions[$id] = array(); + } + + $subscriptions[$id][] = $row['method']; + } + while ($row = $this->db->sql_fetchrow($result)); + } + + $this->db->sql_freeresult($result); + } + } + + return $subscriptions; + } + + /** + * Add a subscription + * + * @param string $item_type Type identifier of the subscription + * @param int $item_id The id of the item + * @param string $method The method of the notification e.g. '', 'email', or 'jabber' + * @param bool|int $user_id The user_id to add the subscription for (bool false for current user) + */ + public function add_subscription($item_type, $item_id = 0, $method = '', $user_id = false) + { + if ($method !== '') + { + // Make sure to subscribe them to the base subscription + $this->add_subscription($item_type, $item_id, '', $user_id); + } + + $user_id = ($user_id === false) ? $this->user->data['user_id'] : $user_id; + + $sql = 'SELECT notify + FROM ' . $this->user_notifications_table . " + WHERE item_type = '" . $this->db->sql_escape($item_type) . "' + AND item_id = " . (int) $item_id . ' + AND user_id = ' .(int) $user_id . " + AND method = '" . $this->db->sql_escape($method) . "'"; + $this->db->sql_query($sql); + $current = $this->db->sql_fetchfield('notify'); + $this->db->sql_freeresult(); + + if ($current === false) + { + $sql = 'INSERT INTO ' . $this->user_notifications_table . ' ' . + $this->db->sql_build_array('INSERT', array( + 'item_type' => $item_type, + 'item_id' => (int) $item_id, + 'user_id' => (int) $user_id, + 'method' => $method, + 'notify' => 1, + )); + $this->db->sql_query($sql); + } + else if (!$current) + { + $sql = 'UPDATE ' . $this->user_notifications_table . " + SET notify = 1 + WHERE item_type = '" . $this->db->sql_escape($item_type) . "' + AND item_id = " . (int) $item_id . ' + AND user_id = ' .(int) $user_id . " + AND method = '" . $this->db->sql_escape($method) . "'"; + $this->db->sql_query($sql); + } + } + + /** + * Delete a subscription + * + * @param string $item_type Type identifier of the subscription + * @param int $item_id The id of the item + * @param string $method The method of the notification e.g. '', 'email', or 'jabber' + * @param bool|int $user_id The user_id to add the subscription for (bool false for current user) + */ + public function delete_subscription($item_type, $item_id = 0, $method = '', $user_id = false) + { + $user_id = ($user_id === false) ? $this->user->data['user_id'] : $user_id; + + // If no method, make sure that no other notification methods for this item are selected before deleting + if ($method === '') + { + $sql = 'SELECT COUNT(*) as num_notifications + FROM ' . $this->user_notifications_table . " + WHERE item_type = '" . $this->db->sql_escape($item_type) . "' + AND item_id = " . (int) $item_id . ' + AND user_id = ' .(int) $user_id . " + AND method <> '' + AND notify = 1"; + $this->db->sql_query($sql); + $num_notifications = $this->db->sql_fetchfield('num_notifications'); + $this->db->sql_freeresult(); + + if ($num_notifications) + { + return; + } + } + + $sql = 'UPDATE ' . $this->user_notifications_table . " + SET notify = 0 + WHERE item_type = '" . $this->db->sql_escape($item_type) . "' + AND item_id = " . (int) $item_id . ' + AND user_id = ' .(int) $user_id . " + AND method = '" . $this->db->sql_escape($method) . "'"; + $this->db->sql_query($sql); + + if (!$this->db->sql_affectedrows()) + { + $sql = 'INSERT INTO ' . $this->user_notifications_table . ' ' . + $this->db->sql_build_array('INSERT', array( + 'item_type' => $item_type, + 'item_id' => (int) $item_id, + 'user_id' => (int) $user_id, + 'method' => $method, + 'notify' => 0, + )); + $this->db->sql_query($sql); + } + } + + /** + * Disable all notifications of a certain type + * + * This should be called when an extension which has notification types + * is disabled so that all those notifications are hidden and do not + * cause errors + * + * @param string $notification_type_name Type identifier of the subscription + */ + public function disable_notifications($notification_type_name) + { + $sql = 'UPDATE ' . $this->notification_types_table . " + SET notification_type_enabled = 0 + WHERE notification_type_name = '" . $this->db->sql_escape($notification_type_name) . "'"; + $this->db->sql_query($sql); + } + + /** + * Purge all notifications of a certain type + * + * This should be called when an extension which has notification types + * is purged so that all those notifications are removed + * + * @param string $notification_type_name Type identifier of the subscription + */ + public function purge_notifications($notification_type_name) + { + $notification_type_id = $this->get_notification_type_id($notification_type_name); + + $sql = 'DELETE FROM ' . $this->notifications_table . ' + WHERE notification_type_id = ' . (int) $notification_type_id; + $this->db->sql_query($sql); + + $sql = 'DELETE FROM ' . $this->notification_types_table . ' + WHERE notification_type_id = ' . (int) $notification_type_id; + $this->db->sql_query($sql); + + $this->cache->destroy('notification_type_ids'); + } + + /** + * Enable all notifications of a certain type + * + * This should be called when an extension which has notification types + * that was disabled is re-enabled so that all those notifications that + * were hidden are shown again + * + * @param string $notification_type_name Type identifier of the subscription + */ + public function enable_notifications($notification_type_name) + { + $sql = 'UPDATE ' . $this->notification_types_table . " + SET notification_type_enabled = 1 + WHERE notification_type_name = '" . $this->db->sql_escape($notification_type_name) . "'"; + $this->db->sql_query($sql); + } + + /** + * Delete all notifications older than a certain time + * + * @param int $timestamp Unix timestamp to delete all notifications that were created before + */ + public function prune_notifications($timestamp) + { + $sql = 'DELETE FROM ' . $this->notifications_table . ' + WHERE notification_time < ' . (int) $timestamp; + $this->db->sql_query($sql); + } + + /** + * Helper to get the notifications item type class and set it up + */ + public function get_item_type_class($notification_type_name, $data = array()) + { + $notification_type_name = (strpos($notification_type_name, 'notification.type.') === 0) ? $notification_type_name : 'notification.type.' . $notification_type_name; + + $item = $this->load_object($notification_type_name); + + $item->set_initial_data($data); + + return $item; + } + + /** + * Helper to get the notifications method class and set it up + */ + public function get_method_class($method_name) + { + $method_name = (strpos($method_name, 'notification.method.') === 0) ? $method_name : 'notification.method.' . $method_name; + + return $this->load_object($method_name); + } + + /** + * Helper to load objects (notification types/methods) + */ + protected function load_object($object_name) + { + $object = $this->phpbb_container->get($object_name); + + if (method_exists($object, 'set_notification_manager')) + { + $object->set_notification_manager($this); + } + + return $object; + } + + /** + * Get the notification type id from the name + * + * @param string $notification_type_name The name + * @return int the notification_type_id + */ + public function get_notification_type_id($notification_type_name) + { + $notification_type_ids = $this->cache->get('notification_type_ids'); + + if ($notification_type_ids === false) + { + $notification_type_ids = array(); + + $sql = 'SELECT notification_type_id, notification_type_name + FROM ' . $this->notification_types_table; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $notification_type_ids[$row['notification_type_name']] = (int) $row['notification_type_id']; + } + $this->db->sql_freeresult($result); + + $this->cache->put('notification_type_ids', $notification_type_ids); + } + + if (!isset($notification_type_ids[$notification_type_name])) + { + if (!isset($this->notification_types[$notification_type_name]) && !isset($this->notification_types['notification.type.' . $notification_type_name])) + { + throw new phpbb_notification_exception($this->user->lang('NOTIFICATION_TYPE_NOT_EXIST', $notification_type_name)); + } + + $sql = 'INSERT INTO ' . $this->notification_types_table . ' ' . $this->db->sql_build_array('INSERT', array( + 'notification_type_name' => $notification_type_name, + 'notification_type_enabled' => 1, + )); + $this->db->sql_query($sql); + + $notification_type_ids[$notification_type_name] = (int) $this->db->sql_nextid(); + + $this->cache->put('notification_type_ids', $notification_type_ids); + } + + return $notification_type_ids[$notification_type_name]; + } + + /** + * Get notification type ids (as an array) + * + * @param array $notification_type_names Array of strings + * @return array Array of integers + */ + public function get_notification_type_ids(array $notification_type_names) + { + $notification_type_ids = array(); + + foreach ($notification_type_names as $name) + { + $notification_type_ids[$name] = $this->get_notification_type_id($name); + } + + return $notification_type_ids; + } +} diff --git a/phpBB/phpbb/notification/method/base.php b/phpBB/phpbb/notification/method/base.php new file mode 100644 index 0000000000..b633956d01 --- /dev/null +++ b/phpBB/phpbb/notification/method/base.php @@ -0,0 +1,116 @@ +user_loader = $user_loader; + $this->db = $db; + $this->cache = $cache; + $this->user = $user; + $this->auth = $auth; + $this->config = $config; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + } + + /** + * Set notification manager (required) + * + * @param phpbb_notification_manager $notification_manager + */ + public function set_notification_manager(phpbb_notification_manager $notification_manager) + { + $this->notification_manager = $notification_manager; + } + + /** + * Add a notification to the queue + * + * @param phpbb_notification_type_interface $notification + */ + public function add_to_queue(phpbb_notification_type_interface $notification) + { + $this->queue[] = $notification; + } + + /** + * Empty the queue + */ + protected function empty_queue() + { + $this->queue = array(); + } +} diff --git a/phpBB/phpbb/notification/method/email.php b/phpBB/phpbb/notification/method/email.php new file mode 100644 index 0000000000..571b0ec656 --- /dev/null +++ b/phpBB/phpbb/notification/method/email.php @@ -0,0 +1,52 @@ +config['email_enable'] && $this->user->data['user_email']; + } + + /** + * Parse the queue and notify the users + */ + public function notify() + { + return $this->notify_using_messenger(NOTIFY_EMAIL); + } +} diff --git a/phpBB/phpbb/notification/method/interface.php b/phpBB/phpbb/notification/method/interface.php new file mode 100644 index 0000000000..ef875942cc --- /dev/null +++ b/phpBB/phpbb/notification/method/interface.php @@ -0,0 +1,48 @@ +global_available() && $this->user->data['user_jabber']); + } + + /** + * Is this method available at all? + * This is checked before notifications are sent + */ + public function global_available() + { + return !( + empty($this->config['jab_enable']) || + empty($this->config['jab_host']) || + empty($this->config['jab_username']) || + empty($this->config['jab_password']) || + !@extension_loaded('xml') + ); + } + + public function notify() + { + if (!$this->global_available()) + { + return; + } + + return $this->notify_using_messenger(NOTIFY_IM, 'short/'); + } +} diff --git a/phpBB/phpbb/notification/method/messenger_base.php b/phpBB/phpbb/notification/method/messenger_base.php new file mode 100644 index 0000000000..4966aa94bc --- /dev/null +++ b/phpBB/phpbb/notification/method/messenger_base.php @@ -0,0 +1,100 @@ +queue)) + { + return; + } + + // Load all users we want to notify (we need their email address) + $user_ids = $users = array(); + foreach ($this->queue as $notification) + { + $user_ids[] = $notification->user_id; + } + + // We do not send emails to banned users + if (!function_exists('phpbb_get_banned_user_ids')) + { + include($this->phpbb_root_path . 'includes/functions_user.' . $this->php_ext); + } + $banned_users = phpbb_get_banned_user_ids($user_ids); + + // Load all the users we need + $this->user_loader->load_users($user_ids); + + // Load the messenger + if (!class_exists('messenger')) + { + include($this->phpbb_root_path . 'includes/functions_messenger.' . $this->php_ext); + } + $messenger = new messenger(); + $board_url = generate_board_url(); + + // Time to go through the queue and send emails + foreach ($this->queue as $notification) + { + if ($notification->get_email_template() === false) + { + continue; + } + + $user = $this->user_loader->get_user($notification->user_id); + + if ($user['user_type'] == USER_IGNORE || in_array($notification->user_id, $banned_users)) + { + continue; + } + + $messenger->template($template_dir_prefix . $notification->get_email_template(), $user['user_lang']); + + $messenger->set_addresses($user); + + $messenger->assign_vars(array_merge(array( + 'USERNAME' => $user['username'], + + 'U_NOTIFICATION_SETTINGS' => generate_board_url() . '/ucp.' . $this->php_ext . '?i=ucp_notifications', + ), $notification->get_email_template_variables())); + + $messenger->send($notify_method); + } + + // Save the queue in the messenger class (has to be called or these emails could be lost?) + $messenger->save_queue(); + + // We're done, empty the queue + $this->empty_queue(); + } +} diff --git a/phpBB/phpbb/notification/type/approve_post.php b/phpBB/phpbb/notification/type/approve_post.php new file mode 100644 index 0000000000..1a30781c35 --- /dev/null +++ b/phpBB/phpbb/notification/type/approve_post.php @@ -0,0 +1,140 @@ + 'moderation_queue', + 'lang' => 'NOTIFICATION_TYPE_MODERATION_QUEUE', + 'group' => 'NOTIFICATION_GROUP_POSTING', + ); + + /** + * Is available + */ + public function is_available() + { + return !$this->auth->acl_get('m_approve'); + } + + /** + * Find the users who want to receive notifications + * + * @param array $post Data from + * + * @return array + */ + public function find_users_for_notification($post, $options = array()) + { + $options = array_merge(array( + 'ignore_users' => array(), + ), $options); + + $users = array(); + $users[$post['poster_id']] = array(''); + + $auth_read = $this->auth->acl_get_list(array_keys($users), 'f_read', $post['forum_id']); + + if (empty($auth_read)) + { + return array(); + } + + return $this->check_user_notification_options($auth_read[$post['forum_id']]['f_read'], array_merge($options, array( + 'item_type' => self::$notification_option['id'], + ))); + } + + /** + * Pre create insert array function + * This allows you to perform certain actions, like run a query + * and load data, before create_insert_array() is run. The data + * returned from this function will be sent to create_insert_array(). + * + * @param array $post Post data from submit_post + * @param array $notify_users Notify users list + * Formated from find_users_for_notification() + * @return array Whatever you want to send to create_insert_array(). + */ + public function pre_create_insert_array($post, $notify_users) + { + // In the parent class, this is used to check if the post is already + // read by a user and marks the notification read if it was marked read. + // Returning an empty array in effect, forces it to be marked as unread + // (and also saves a query) + return array(); + } + + /** + * Function for preparing the data for insertion in an SQL query + * (The service handles insertion) + * + * @param array $post Data from submit_post + * @param array $pre_create_data Data from pre_create_insert_array() + * + * @return array Array of data ready to be inserted into the database + */ + public function create_insert_array($post, $pre_create_data = array()) + { + $this->set_data('post_subject', $post['post_subject']); + + $data = parent::create_insert_array($post, $pre_create_data); + + $this->notification_time = $data['notification_time'] = time(); + + return $data; + } + + /** + * Get email template + * + * @return string|bool + */ + public function get_email_template() + { + return 'post_approved'; + } +} diff --git a/phpBB/phpbb/notification/type/approve_topic.php b/phpBB/phpbb/notification/type/approve_topic.php new file mode 100644 index 0000000000..e728e9ac30 --- /dev/null +++ b/phpBB/phpbb/notification/type/approve_topic.php @@ -0,0 +1,138 @@ + 'moderation_queue', + 'lang' => 'NOTIFICATION_TYPE_MODERATION_QUEUE', + 'group' => 'NOTIFICATION_GROUP_POSTING', + ); + + /** + * Is available + */ + public function is_available() + { + return !$this->auth->acl_get('m_approve'); + } + + /** + * Find the users who want to receive notifications + * + * @param array $post Data from + * + * @return array + */ + public function find_users_for_notification($post, $options = array()) + { + $options = array_merge(array( + 'ignore_users' => array(), + ), $options); + + $users = array(); + $users[$post['poster_id']] = array(''); + + $auth_read = $this->auth->acl_get_list(array_keys($users), 'f_read', $post['forum_id']); + + if (empty($auth_read)) + { + return array(); + } + + return $this->check_user_notification_options($auth_read[$post['forum_id']]['f_read'], array_merge($options, array( + 'item_type' => self::$notification_option['id'], + ))); + } + + /** + * Pre create insert array function + * This allows you to perform certain actions, like run a query + * and load data, before create_insert_array() is run. The data + * returned from this function will be sent to create_insert_array(). + * + * @param array $post Post data from submit_post + * @param array $notify_users Notify users list + * Formated from find_users_for_notification() + * @return array Whatever you want to send to create_insert_array(). + */ + public function pre_create_insert_array($post, $notify_users) + { + // In the parent class, this is used to check if the post is already + // read by a user and marks the notification read if it was marked read. + // Returning an empty array in effect, forces it to be marked as unread + // (and also saves a query) + return array(); + } + + /** + * Function for preparing the data for insertion in an SQL query + * (The service handles insertion) + * + * @param array $post Data from submit_post + * @param array $pre_create_data Data from pre_create_insert_array() + * + * @return array Array of data ready to be inserted into the database + */ + public function create_insert_array($post, $pre_create_data = array()) + { + $data = parent::create_insert_array($post, $pre_create_data); + + $this->notification_time = $data['notification_time'] = time(); + + return $data; + } + + /** + * Get email template + * + * @return string|bool + */ + public function get_email_template() + { + return 'topic_approved'; + } +} diff --git a/phpBB/phpbb/notification/type/base.php b/phpBB/phpbb/notification/type/base.php new file mode 100644 index 0000000000..46517f1c9b --- /dev/null +++ b/phpBB/phpbb/notification/type/base.php @@ -0,0 +1,489 @@ + forum_id, for post => topic_id, etc) + * user_id + * notification_read + * notification_time + * notification_data (special serialized field that each notification type can use to store stuff) + * + * @var array $data Notification row from the database + * This must be private, all interaction should use __get(), __set(), get_data(), set_data() + */ + private $data = array(); + + /** + * Notification Type Base Constructor + * + * @param phpbb_user_loader $user_loader + * @param phpbb_db_driver $db + * @param phpbb_cache_driver_interface $cache + * @param phpbb_user $user + * @param phpbb_auth $auth + * @param phpbb_config $config + * @param string $phpbb_root_path + * @param string $php_ext + * @param string $notification_types_table + * @param string $notifications_table + * @param string $user_notifications_table + * @return phpbb_notification_type_base + */ + public function __construct(phpbb_user_loader $user_loader, phpbb_db_driver $db, phpbb_cache_driver_interface $cache, $user, phpbb_auth $auth, phpbb_config $config, $phpbb_root_path, $php_ext, $notification_types_table, $notifications_table, $user_notifications_table) + { + $this->user_loader = $user_loader; + $this->db = $db; + $this->cache = $cache; + $this->user = $user; + $this->auth = $auth; + $this->config = $config; + + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + + $this->notification_types_table = $notification_types_table; + $this->notifications_table = $notifications_table; + $this->user_notifications_table = $user_notifications_table; + } + + /** + * Set notification manager (required) + * + * @param phpbb_notification_manager $notification_manager + */ + public function set_notification_manager(phpbb_notification_manager $notification_manager) + { + $this->notification_manager = $notification_manager; + + $this->notification_type_id = $this->notification_manager->get_notification_type_id($this->get_type()); + } + + /** + * Set initial data from the database + * + * @param array $data Row directly from the database + */ + public function set_initial_data($data = array()) + { + // The row from the database (unless this is a new notification we're going to add) + $this->data = $data; + $this->data['notification_data'] = (isset($this->data['notification_data'])) ? unserialize($this->data['notification_data']) : array(); + } + + /** + * Magic method to get data from this notification + * + * @param mixed $name + * @return mixed + */ + public function __get($name) + { + return (!isset($this->data[$name])) ? null : $this->data[$name]; + } + + + /** + * Magic method to set data on this notification + * + * @param mixed $name + * @return null + */ + public function __set($name, $value) + { + $this->data[$name] = $value; + } + + + /** + * Magic method to get a string of this notification + * + * Primarily for testing + * + * @param string $name + * @return mixed + */ + public function __toString() + { + return (!empty($this->data)) ? var_export($this->data, true) : $this->get_type(); + } + + /** + * Get special data (only important for the classes that extend this) + * + * @param string $name Name of the variable to get + * @return mixed + */ + protected function get_data($name) + { + return ($name === false) ? $this->data['notification_data'] : ((isset($this->data['notification_data'][$name])) ? $this->data['notification_data'][$name] : null); + } + + /** + * Set special data (only important for the classes that extend this) + * + * @param string $name Name of the variable to set + * @param mixed $value Value to set to the variable + * @return mixed + */ + protected function set_data($name, $value) + { + $this->data['notification_data'][$name] = $value; + } + + /** + * Function for preparing the data for insertion in an SQL query + * (The service handles insertion) + * + * @param array $type_data Data unique to this notification type + * @param array $pre_create_data Data from pre_create_insert_array() + * @return array Array of data ready to be inserted into the database + */ + public function create_insert_array($type_data, $pre_create_data = array()) + { + // Defaults + $this->data = array_merge(array( + 'item_id' => static::get_item_id($type_data), + 'notification_type_id' => $this->notification_type_id, + 'item_parent_id' => static::get_item_parent_id($type_data), + + 'notification_time' => time(), + 'notification_read' => false, + + 'notification_data' => array(), + ), $this->data); + + $data = $this->data; + + $data['notification_data'] = serialize($data['notification_data']); + + return $data; + } + + /** + * Function for preparing the data for update in an SQL query + * (The service handles insertion) + * + * @param array $type_data Data unique to this notification type + * @return array Array of data ready to be updated in the database + */ + public function create_update_array($type_data) + { + $data = $this->create_insert_array($type_data); + + // Unset data unique to each row + unset( + $data['notification_time'], // Also unsetting time, since it always tries to change the time to current (if you actually need to change the time, over-ride this function) + $data['notification_id'], + $data['notification_read'], + $data['user_id'] + ); + + return $data; + } + + /** + * Mark this item read + * + * @param bool $return True to return a string containing the SQL code to update this item, False to execute it (Default: False) + * @return string|null If $return is False, nothing will be returned, else the sql code to update this item + */ + public function mark_read($return = false) + { + return $this->mark(false, $return); + } + + /** + * Mark this item unread + * + * @param bool $return True to return a string containing the SQL code to update this item, False to execute it (Default: False) + * @return string|null If $return is False, nothing will be returned, else the sql code to update this item + */ + public function mark_unread($return = false) + { + return $this->mark(true, $return); + } + + /** + * Prepare to output the notification to the template + * + * @return array Template variables + */ + public function prepare_for_display() + { + if ($this->get_url()) + { + $u_mark_read = append_sid($this->phpbb_root_path . 'index.' . $this->php_ext, 'mark_notification=' . $this->notification_id); + } + else + { + $redirect = (($this->user->page['page_dir']) ? $this->user->page['page_dir'] . '/' : '') . $this->user->page['page_name'] . (($this->user->page['query_string']) ? '?' . $this->user->page['query_string'] : ''); + + $u_mark_read = append_sid($this->phpbb_root_path . 'index.' . $this->php_ext, 'mark_notification=' . $this->notification_id . '&redirect=' . urlencode($redirect)); + } + + return array( + 'NOTIFICATION_ID' => $this->notification_id, + + 'AVATAR' => $this->get_avatar(), + + 'FORMATTED_TITLE' => $this->get_title(), + + 'URL' => $this->get_url(), + 'TIME' => $this->user->format_date($this->notification_time), + + 'UNREAD' => !$this->notification_read, + + 'U_MARK_READ' => (!$this->notification_read) ? $u_mark_read : '', + ); + } + + /** + * -------------- Fall back functions ------------------- + */ + + /** + * URL to unsubscribe to this notification (fall back) + * + * @param string|bool $method Method name to unsubscribe from (email|jabber|etc), False to unsubscribe from all notifications for this item + */ + public function get_unsubscribe_url($method = false) + { + return false; + } + + /** + * Get the user's avatar (fall back) + * + * @return string + */ + public function get_avatar() + { + return ''; + } + + /** + * Get the special items to load (fall back) + * + * @return array + */ + public function get_load_special() + { + return array(); + } + + /** + * Load the special items (fall back) + */ + public function load_special($data, $notifications) + { + return; + } + + /** + * Is available (fall back) + * + * @return bool + */ + public function is_available() + { + return true; + } + + /** + * Pre create insert array function (fall back) + * + * @return array + */ + public function pre_create_insert_array($type_data, $notify_users) + { + return array(); + } + + /** + * -------------- Helper functions ------------------- + */ + + /** + * Find the users who want to receive notifications (helper) + * + * @param array $user_ids User IDs to check if they want to receive notifications + * (Bool False to check all users besides anonymous and bots (USER_IGNORE)) + * + * @return array + */ + protected function check_user_notification_options($user_ids = false, $options = array()) + { + $options = array_merge(array( + 'ignore_users' => array(), + 'item_type' => $this->get_type(), + 'item_id' => 0, // Global by default + ), $options); + + if ($user_ids === false) + { + $user_ids = array(); + + $sql = 'SELECT user_id + FROM ' . USERS_TABLE . ' + WHERE user_id <> ' . ANONYMOUS . ' + AND user_type <> ' . USER_IGNORE; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $user_ids[] = $row['user_id']; + } + $this->db->sql_freeresult($result); + } + + if (empty($user_ids)) + { + return array(); + } + + $rowset = $resulting_user_ids = array(); + + $sql = 'SELECT user_id, method, notify + FROM ' . $this->user_notifications_table . ' + WHERE ' . $this->db->sql_in_set('user_id', $user_ids) . " + AND item_type = '" . $this->db->sql_escape($options['item_type']) . "' + AND item_id = " . (int) $options['item_id']; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $resulting_user_ids[] = $row['user_id']; + + if (!$row['notify'] || (isset($options['ignore_users'][$row['user_id']]) && in_array($row['method'], $options['ignore_users'][$row['user_id']]))) + { + continue; + } + + if (!isset($rowset[$row['user_id']])) + { + $rowset[$row['user_id']] = array(); + } + + $rowset[$row['user_id']][] = $row['method']; + } + + $this->db->sql_freeresult($result); + + foreach ($user_ids as $user_id) + { + if (!in_array($user_id, $resulting_user_ids) && !isset($options['ignore_users'][$user_id])) + { + // No rows at all for this user, default to '' + $rowset[$user_id] = array(''); + } + } + + return $rowset; + } + + /** + * Mark this item read/unread helper + * + * @param bool $unread Unread (True/False) (Default: False) + * @param bool $return True to return a string containing the SQL code to update this item, False to execute it (Default: False) + * @return string|null If $return is False, nothing will be returned, else the sql code to update this item + */ + protected function mark($unread = true, $return = false) + { + $this->notification_read = (bool) !$unread; + + $where = array( + 'notification_type_id = ' . (int) $this->notification_type_id, + 'item_id = ' . (int) $this->item_id, + 'user_id = ' . (int) $this->user_id, + ); + $where = implode(' AND ', $where); + + if ($return) + { + return $where; + } + + $sql = 'UPDATE ' . $this->notifications_table . ' + SET notification_read = ' . (int) $this->notification_read . ' + WHERE ' . $where; + $this->db->sql_query($sql); + } +} diff --git a/phpBB/phpbb/notification/type/bookmark.php b/phpBB/phpbb/notification/type/bookmark.php new file mode 100644 index 0000000000..ae2e75d3eb --- /dev/null +++ b/phpBB/phpbb/notification/type/bookmark.php @@ -0,0 +1,138 @@ + 'NOTIFICATION_TYPE_BOOKMARK', + 'group' => 'NOTIFICATION_GROUP_POSTING', + ); + + /** + * Is available + */ + public function is_available() + { + return $this->config['allow_bookmarks']; + } + + /** + * Find the users who want to receive notifications + * + * @param array $post Data from + * + * @return array + */ + public function find_users_for_notification($post, $options = array()) + { + $options = array_merge(array( + 'ignore_users' => array(), + ), $options); + + $users = array(); + + $sql = 'SELECT user_id + FROM ' . BOOKMARKS_TABLE . ' + WHERE ' . $this->db->sql_in_set('topic_id', $post['topic_id']) . ' + AND user_id <> ' . (int) $post['poster_id']; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $users[] = $row['user_id']; + } + $this->db->sql_freeresult($result); + + if (empty($users)) + { + return array(); + } + sort($users); + + $auth_read = $this->auth->acl_get_list($users, 'f_read', $post['forum_id']); + + if (empty($auth_read)) + { + return array(); + } + + $notify_users = $this->check_user_notification_options($auth_read[$post['forum_id']]['f_read'], $options); + + // Try to find the users who already have been notified about replies and have not read the topic since and just update their notifications + $update_notifications = array(); + $sql = 'SELECT n.* + FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . ' nt + WHERE n.notification_type_id = ' . (int) $this->notification_type_id . ' + AND n.item_parent_id = ' . (int) self::get_item_parent_id($post) . ' + AND n.notification_read = 0 + AND nt.notification_type_id = n.notification_type_id + AND nt.notification_type_enabled = 1'; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + // Do not create a new notification + unset($notify_users[$row['user_id']]); + + $notification = $this->notification_manager->get_item_type_class($this->get_type(), $row); + $sql = 'UPDATE ' . $this->notifications_table . ' + SET ' . $this->db->sql_build_array('UPDATE', $notification->add_responders($post)) . ' + WHERE notification_id = ' . $row['notification_id']; + $this->db->sql_query($sql); + } + $this->db->sql_freeresult($result); + + return $notify_users; + } + + /** + * Get email template + * + * @return string|bool + */ + public function get_email_template() + { + return 'bookmark'; + } +} diff --git a/phpBB/phpbb/notification/type/disapprove_post.php b/phpBB/phpbb/notification/type/disapprove_post.php new file mode 100644 index 0000000000..951c7e0254 --- /dev/null +++ b/phpBB/phpbb/notification/type/disapprove_post.php @@ -0,0 +1,120 @@ + 'moderation_queue', + 'lang' => 'NOTIFICATION_TYPE_MODERATION_QUEUE', + 'group' => 'NOTIFICATION_GROUP_POSTING', + ); + + /** + * Get the HTML formatted title of this notification + * + * @return string + */ + public function get_title() + { + return $this->user->lang( + $this->language_key, + censor_text($this->get_data('topic_title')), + $this->get_data('disapprove_reason') + ); + } + + /** + * Get the url to this item + * + * @return string URL + */ + public function get_url() + { + return ''; + } + + /** + * Get email template variables + * + * @return array + */ + public function get_email_template_variables() + { + return array_merge(parent::get_email_template_variables(), array( + 'REASON' => htmlspecialchars_decode($this->get_data('disapprove_reason')), + )); + } + + /** + * Function for preparing the data for insertion in an SQL query + * (The service handles insertion) + * + * @param array $post Data from submit_post + * @param array $pre_create_data Data from pre_create_insert_array() + * + * @return array Array of data ready to be inserted into the database + */ + public function create_insert_array($post, $pre_create_data = array()) + { + $this->set_data('disapprove_reason', $post['disapprove_reason']); + + $data = parent::create_insert_array($post); + + $this->notification_time = $data['notification_time'] = time(); + + return $data; + } + + /** + * Get email template + * + * @return string|bool + */ + public function get_email_template() + { + return 'post_disapproved'; + } +} diff --git a/phpBB/phpbb/notification/type/disapprove_topic.php b/phpBB/phpbb/notification/type/disapprove_topic.php new file mode 100644 index 0000000000..038e528797 --- /dev/null +++ b/phpBB/phpbb/notification/type/disapprove_topic.php @@ -0,0 +1,120 @@ + 'moderation_queue', + 'lang' => 'NOTIFICATION_TYPE_MODERATION_QUEUE', + 'group' => 'NOTIFICATION_GROUP_POSTING', + ); + + /** + * Get the HTML formatted title of this notification + * + * @return string + */ + public function get_title() + { + return $this->user->lang( + $this->language_key, + censor_text($this->get_data('topic_title')), + $this->get_data('disapprove_reason') + ); + } + + /** + * Get the url to this item + * + * @return string URL + */ + public function get_url() + { + return ''; + } + + /** + * Get email template variables + * + * @return array + */ + public function get_email_template_variables() + { + return array_merge(parent::get_email_template_variables(), array( + 'REASON' => htmlspecialchars_decode($this->get_data('disapprove_reason')), + )); + } + + /** + * Function for preparing the data for insertion in an SQL query + * (The service handles insertion) + * + * @param array $post Data from submit_post + * @param array $pre_create_data Data from pre_create_insert_array() + * + * @return array Array of data ready to be inserted into the database + */ + public function create_insert_array($post, $pre_create_data = array()) + { + $this->set_data('disapprove_reason', $post['disapprove_reason']); + + $data = parent::create_insert_array($post, $pre_create_data); + + $this->notification_time = $data['notification_time'] = time(); + + return $data; + } + + /** + * Get email template + * + * @return string|bool + */ + public function get_email_template() + { + return 'topic_disapproved'; + } +} diff --git a/phpBB/phpbb/notification/type/interface.php b/phpBB/phpbb/notification/type/interface.php new file mode 100644 index 0000000000..a40fdafd09 --- /dev/null +++ b/phpBB/phpbb/notification/type/interface.php @@ -0,0 +1,189 @@ + array of users and user types that should not receive notifications from this type because they've already been notified + * e.g.: array(2 => array(''), 3 => array('', 'email'), ...) + * + * @return array + */ + public function find_users_for_notification($type_data, $options); + + /** + * Users needed to query before this notification can be displayed + * + * @return array Array of user_ids + */ + public function users_to_query(); + + /** + * Get the special items to load + * + * @return array Data will be combined sent to load_special() so you can run a single query and get data required for this notification type + */ + public function get_load_special(); + + /** + * Load the special items + * + * @param array $data Data from get_load_special() + * @param array $notifications Array of notifications (key is notification_id, value is the notification objects) + */ + public function load_special($data, $notifications); + + /** + * Get the HTML formatted title of this notification + * + * @return string + */ + public function get_title(); + + /** + * Get the url to this item + * + * @return string URL + */ + public function get_url(); + + /** + * URL to unsubscribe to this notification + * + * @param string|bool $method Method name to unsubscribe from (email|jabber|etc), False to unsubscribe from all notifications for this item + */ + public function get_unsubscribe_url($method); + + /** + * Get the user's avatar (the user who caused the notification typically) + * + * @return string + */ + public function get_avatar(); + + /** + * Prepare to output the notification to the template + */ + public function prepare_for_display(); + + /** + * Get email template + * + * @return string|bool + */ + public function get_email_template(); + + /** + * Get email template variables + * + * @return array + */ + public function get_email_template_variables(); + + /** + * Pre create insert array function + * This allows you to perform certain actions, like run a query + * and load data, before create_insert_array() is run. The data + * returned from this function will be sent to create_insert_array(). + * + * @param array $type_data The type specific data + * @param array $notify_users Notify users list + * Formated from find_users_for_notification() + * @return array Whatever you want to send to create_insert_array(). + */ + public function pre_create_insert_array($type_data, $notify_users); + + /** + * Function for preparing the data for insertion in an SQL query + * (The service handles insertion) + * + * @param array $type_data The type specific data + * @param array $pre_create_data Data from pre_create_insert_array() + * + * @return array Array of data ready to be inserted into the database + */ + public function create_insert_array($type_data, $pre_create_data); + + /** + * Function for preparing the data for update in an SQL query + * (The service handles insertion) + * + * @param array $type_data Data unique to this notification type + * + * @return array Array of data ready to be updated in the database + */ + public function create_update_array($type_data); + + /** + * Mark this item read + * + * @param bool $return True to return a string containing the SQL code to update this item, False to execute it (Default: False) + * @return string + */ + public function mark_read($return); + + /** + * Mark this item unread + * + * @param bool $return True to return a string containing the SQL code to update this item, False to execute it (Default: False) + * @return string + */ + public function mark_unread($return); +} diff --git a/phpBB/phpbb/notification/type/pm.php b/phpBB/phpbb/notification/type/pm.php new file mode 100644 index 0000000000..b3db7ad5ad --- /dev/null +++ b/phpBB/phpbb/notification/type/pm.php @@ -0,0 +1,184 @@ + 'NOTIFICATION_TYPE_PM', + ); + + /** + * Is available + */ + public function is_available() + { + return ($this->config['allow_privmsg'] && $this->auth->acl_get('u_readpm')); + } + + /** + * Get the id of the + * + * @param array $pm The data from the private message + */ + public static function get_item_id($pm) + { + return (int) $pm['msg_id']; + } + + /** + * Get the id of the parent + * + * @param array $pm The data from the pm + */ + public static function get_item_parent_id($pm) + { + // No parent + return 0; + } + + /** + * Find the users who want to receive notifications + * + * @param array $pm Data from + * + * @return array + */ + public function find_users_for_notification($pm, $options = array()) + { + $options = array_merge(array( + 'ignore_users' => array(), + ), $options); + + if (!sizeof($pm['recipients'])) + { + return array(); + } + + unset($pm['recipients'][$pm['from_user_id']]); + + $this->user_loader->load_users(array_keys($pm['recipients'])); + + return $this->check_user_notification_options(array_keys($pm['recipients']), $options); + } + + /** + * Get the user's avatar + */ + public function get_avatar() + { + return $this->user_loader->get_avatar($this->get_data('from_user_id')); + } + + /** + * Get the HTML formatted title of this notification + * + * @return string + */ + public function get_title() + { + $username = $this->user_loader->get_username($this->get_data('from_user_id'), 'no_profile'); + + return $this->user->lang('NOTIFICATION_PM', $username, $this->get_data('message_subject')); + } + + /** + * Get email template + * + * @return string|bool + */ + public function get_email_template() + { + return 'privmsg_notify'; + } + + /** + * Get email template variables + * + * @return array + */ + public function get_email_template_variables() + { + $user_data = $this->user_loader->get_user($this->get_data('from_user_id')); + + return array( + 'AUTHOR_NAME' => htmlspecialchars_decode($user_data['username']), + 'SUBJECT' => htmlspecialchars_decode(censor_text($this->get_data('message_subject'))), + + 'U_VIEW_MESSAGE' => generate_board_url() . '/ucp.' . $this->php_ext . "?i=pm&mode=view&p={$this->item_id}", + ); + } + + /** + * Get the url to this item + * + * @return string URL + */ + public function get_url() + { + return append_sid($this->phpbb_root_path . 'ucp.' . $this->php_ext, "i=pm&mode=view&p={$this->item_id}"); + } + + /** + * Users needed to query before this notification can be displayed + * + * @return array Array of user_ids + */ + public function users_to_query() + { + return array($this->get_data('from_user_id')); + } + + /** + * Function for preparing the data for insertion in an SQL query + * (The service handles insertion) + * + * @param array $post Data from submit_post + * @param array $pre_create_data Data from pre_create_insert_array() + * + * @return array Array of data ready to be inserted into the database + */ + public function create_insert_array($pm, $pre_create_data = array()) + { + $this->set_data('from_user_id', $pm['from_user_id']); + + $this->set_data('message_subject', $pm['message_subject']); + + return parent::create_insert_array($pm, $pre_create_data); + } +} diff --git a/phpBB/phpbb/notification/type/post.php b/phpBB/phpbb/notification/type/post.php new file mode 100644 index 0000000000..9207fd866e --- /dev/null +++ b/phpBB/phpbb/notification/type/post.php @@ -0,0 +1,385 @@ + 'NOTIFICATION_TYPE_POST', + 'group' => 'NOTIFICATION_GROUP_POSTING', + ); + + /** + * Is available + */ + public function is_available() + { + return $this->config['allow_topic_notify']; + } + + /** + * Get the id of the item + * + * @param array $post The data from the post + */ + public static function get_item_id($post) + { + return (int) $post['post_id']; + } + + /** + * Get the id of the parent + * + * @param array $post The data from the post + */ + public static function get_item_parent_id($post) + { + return (int) $post['topic_id']; + } + + /** + * Find the users who want to receive notifications + * + * @param array $post Data from + * + * @return array + */ + public function find_users_for_notification($post, $options = array()) + { + $options = array_merge(array( + 'ignore_users' => array(), + ), $options); + + $users = array(); + + $sql = 'SELECT user_id + FROM ' . TOPICS_WATCH_TABLE . ' + WHERE topic_id = ' . (int) $post['topic_id'] . ' + AND notify_status = ' . NOTIFY_YES . ' + AND user_id <> ' . (int) $post['poster_id']; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $users[] = $row['user_id']; + } + $this->db->sql_freeresult($result); + + $sql = 'SELECT user_id + FROM ' . FORUMS_WATCH_TABLE . ' + WHERE forum_id = ' . (int) $post['forum_id'] . ' + AND notify_status = ' . NOTIFY_YES . ' + AND user_id <> ' . (int) $post['poster_id']; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $users[] = $row['user_id']; + } + $this->db->sql_freeresult($result); + + if (empty($users)) + { + return array(); + } + + $users = array_unique($users); + sort($users); + + $auth_read = $this->auth->acl_get_list($users, 'f_read', $post['forum_id']); + + if (empty($auth_read)) + { + return array(); + } + + $notify_users = $this->check_user_notification_options($auth_read[$post['forum_id']]['f_read'], $options); + + // Try to find the users who already have been notified about replies and have not read the topic since and just update their notifications + $update_notifications = array(); + $sql = 'SELECT n.* + FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . ' nt + WHERE n.notification_type_id = ' . (int) $this->notification_type_id . ' + AND n.item_parent_id = ' . (int) self::get_item_parent_id($post) . ' + AND n.notification_read = 0 + AND nt.notification_type_id = n.notification_type_id + AND nt.notification_type_enabled = 1'; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + // Do not create a new notification + unset($notify_users[$row['user_id']]); + + $notification = $this->notification_manager->get_item_type_class($this->get_type(), $row); + $sql = 'UPDATE ' . $this->notifications_table . ' + SET ' . $this->db->sql_build_array('UPDATE', $notification->add_responders($post)) . ' + WHERE notification_id = ' . $row['notification_id']; + $this->db->sql_query($sql); + } + $this->db->sql_freeresult($result); + + return $notify_users; + } + + /** + * Get the user's avatar + */ + public function get_avatar() + { + return $this->user_loader->get_avatar($this->get_data('poster_id')); + } + + /** + * Get the HTML formatted title of this notification + * + * @return string + */ + public function get_title() + { + $responders = $this->get_data('responders'); + $usernames = array(); + + if (!is_array($responders)) + { + $responders = array(); + } + + $responders = array_merge(array(array( + 'poster_id' => $this->get_data('poster_id'), + 'username' => $this->get_data('post_username'), + )), $responders); + + foreach ($responders as $responder) + { + if ($responder['username']) + { + $usernames[] = $responder['username']; + } + else + { + $usernames[] = $this->user_loader->get_username($responder['poster_id'], 'no_profile'); + } + } + + return $this->user->lang( + $this->language_key, + implode(', ', $usernames), + censor_text($this->get_data('topic_title')) + ); + } + + /** + * Get email template + * + * @return string|bool + */ + public function get_email_template() + { + return 'topic_notify'; + } + + /** + * Get email template variables + * + * @return array + */ + public function get_email_template_variables() + { + if ($this->get_data('post_username')) + { + $username = $this->get_data('post_username'); + } + else + { + $username = $this->user_loader->get_username($this->get_data('poster_id'), 'username'); + } + + return array( + 'AUTHOR_NAME' => htmlspecialchars_decode($username), + 'POST_SUBJECT' => htmlspecialchars_decode(censor_text($this->get_data('post_subject'))), + 'TOPIC_TITLE' => htmlspecialchars_decode(censor_text($this->get_data('topic_title'))), + + 'U_VIEW_POST' => generate_board_url() . "/viewtopic.{$this->php_ext}?p={$this->item_id}#p{$this->item_id}", + 'U_NEWEST_POST' => generate_board_url() . "/viewtopic.{$this->php_ext}?f={$this->get_data('forum_id')}&t={$this->item_parent_id}&view=unread#unread", + 'U_TOPIC' => generate_board_url() . "/viewtopic.{$this->php_ext}?f={$this->get_data('forum_id')}&t={$this->item_parent_id}", + 'U_VIEW_TOPIC' => generate_board_url() . "/viewtopic.{$this->php_ext}?f={$this->get_data('forum_id')}&t={$this->item_parent_id}", + 'U_FORUM' => generate_board_url() . "/viewforum.{$this->php_ext}?f={$this->get_data('forum_id')}", + 'U_STOP_WATCHING_TOPIC' => generate_board_url() . "/viewtopic.{$this->php_ext}?uid={$this->user_id}&f={$this->get_data('forum_id')}&t={$this->item_parent_id}&unwatch=topic", + ); + } + + /** + * Get the url to this item + * + * @return string URL + */ + public function get_url() + { + return append_sid($this->phpbb_root_path . 'viewtopic.' . $this->php_ext, "p={$this->item_id}#p{$this->item_id}"); + } + + /** + * Users needed to query before this notification can be displayed + * + * @return array Array of user_ids + */ + public function users_to_query() + { + $responders = $this->get_data('responders'); + $users = array( + $this->get_data('poster_id'), + ); + + if (is_array($responders)) + { + foreach ($responders as $responder) + { + $users[] = $responder['poster_id']; + } + } + + return $users; + } + + /** + * Pre create insert array function + * This allows you to perform certain actions, like run a query + * and load data, before create_insert_array() is run. The data + * returned from this function will be sent to create_insert_array(). + * + * @param array $post Post data from submit_post + * @param array $notify_users Notify users list + * Formated from find_users_for_notification() + * @return array Whatever you want to send to create_insert_array(). + */ + public function pre_create_insert_array($post, $notify_users) + { + if (!sizeof($notify_users)) + { + return array(); + } + + $tracking_data = array(); + $sql = 'SELECT user_id, mark_time FROM ' . TOPICS_TRACK_TABLE . ' + WHERE topic_id = ' . (int) $post['topic_id'] . ' + AND ' . $this->db->sql_in_set('user_id', array_keys($notify_users)); + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $tracking_data[$row['user_id']] = $row['mark_time']; + } + + return $tracking_data; + } + + /** + * Function for preparing the data for insertion in an SQL query + * (The service handles insertion) + * + * @param array $post Data from submit_post + * @param array $pre_create_data Data from pre_create_insert_array() + * + * @return array Array of data ready to be inserted into the database + */ + public function create_insert_array($post, $pre_create_data = array()) + { + $this->set_data('poster_id', $post['poster_id']); + + $this->set_data('topic_title', $post['topic_title']); + + $this->set_data('post_subject', $post['post_subject']); + + $this->set_data('post_username', (($post['poster_id'] == ANONYMOUS) ? $post['post_username'] : '')); + + $this->set_data('forum_id', $post['forum_id']); + + $this->set_data('forum_name', $post['forum_name']); + + $this->notification_time = $post['post_time']; + + // Topics can be "read" before they are public (while awaiting approval). + // Make sure that if the user has read the topic, it's marked as read in the notification + if (isset($pre_create_data[$this->user_id]) && $pre_create_data[$this->user_id] >= $this->notification_time) + { + $this->notification_read = true; + } + + return parent::create_insert_array($post, $pre_create_data); + } + + /** + * Add responders to the notification + * + * @param mixed $post + */ + public function add_responders($post) + { + // Do not add them as a responder if they were the original poster that created the notification + if ($this->get_data('poster_id') == $post['poster_id']) + { + return array('notification_data' => serialize($this->get_data(false))); + } + + $responders = $this->get_data('responders'); + + $responders = ($responders === null) ? array() : $responders; + + foreach ($responders as $responder) + { + // Do not add them as a responder multiple times + if ($responder['poster_id'] == $post['poster_id']) + { + return array('notification_data' => serialize($this->get_data(false))); + } + } + + $responders[] = array( + 'poster_id' => $post['poster_id'], + 'username' => (($post['poster_id'] == ANONYMOUS) ? $post['post_username'] : ''), + ); + + $this->set_data('responders', $responders); + + return array('notification_data' => serialize($this->get_data(false))); + } +} diff --git a/phpBB/phpbb/notification/type/post_in_queue.php b/phpBB/phpbb/notification/type/post_in_queue.php new file mode 100644 index 0000000000..bc4b15cdc3 --- /dev/null +++ b/phpBB/phpbb/notification/type/post_in_queue.php @@ -0,0 +1,154 @@ + 'needs_approval', + 'lang' => 'NOTIFICATION_TYPE_IN_MODERATION_QUEUE', + 'group' => 'NOTIFICATION_GROUP_MODERATION', + ); + + /** + * Permission to check for (in find_users_for_notification) + * + * @var string Permission name + */ + protected $permission = 'm_approve'; + + /** + * Is available + */ + public function is_available() + { + $has_permission = $this->auth->acl_getf($this->permission, true); + + return (!empty($has_permission)); + } + + /** + * Find the users who want to receive notifications + * + * @param array $post Data from the post + * + * @return array + */ + public function find_users_for_notification($post, $options = array()) + { + $options = array_merge(array( + 'ignore_users' => array(), + ), $options); + + // 0 is for global moderator permissions + $auth_approve = $this->auth->acl_get_list(false, $this->permission, array($post['forum_id'], 0)); + + if (empty($auth_approve)) + { + return array(); + } + + $has_permission = array(); + + if (isset($auth_approve[$post['forum_id']][$this->permission])) + { + $has_permission = $auth_approve[$post['forum_id']][$this->permission]; + } + + if (isset($auth_approve[0][$this->permission])) + { + $has_permission = array_unique(array_merge($has_permission, $auth_approve[0][$this->permission])); + } + sort($has_permission); + + $auth_read = $this->auth->acl_get_list($has_permission, 'f_read', $post['forum_id']); + if (empty($auth_read)) + { + return array(); + } + + return $this->check_user_notification_options($auth_read[$post['forum_id']]['f_read'], array_merge($options, array( + 'item_type' => self::$notification_option['id'], + ))); + } + + /** + * Get the url to this item + * + * @return string URL + */ + public function get_url() + { + return append_sid($this->phpbb_root_path . 'mcp.' . $this->php_ext, "i=queue&mode=approve_details&f={$this->get_data('forum_id')}&p={$this->item_id}"); + } + + /** + * Function for preparing the data for insertion in an SQL query + * (The service handles insertion) + * + * @param array $post Data from submit_post + * @param array $pre_create_data Data from pre_create_insert_array() + * + * @return array Array of data ready to be inserted into the database + */ + public function create_insert_array($post, $pre_create_data = array()) + { + $data = parent::create_insert_array($post, $pre_create_data); + + $this->notification_time = $data['notification_time'] = time(); + + return $data; + } + + /** + * Get email template + * + * @return string|bool + */ + public function get_email_template() + { + return 'post_in_queue'; + } +} diff --git a/phpBB/phpbb/notification/type/quote.php b/phpBB/phpbb/notification/type/quote.php new file mode 100644 index 0000000000..0ed13f36fb --- /dev/null +++ b/phpBB/phpbb/notification/type/quote.php @@ -0,0 +1,222 @@ + 'NOTIFICATION_TYPE_QUOTE', + 'group' => 'NOTIFICATION_GROUP_POSTING', + ); + + /** + * Is available + */ + public function is_available() + { + return true; + } + + /** + * Find the users who want to receive notifications + * + * @param array $post Data from + * + * @return array + */ + public function find_users_for_notification($post, $options = array()) + { + $options = array_merge(array( + 'ignore_users' => array(), + ), $options); + + $usernames = false; + preg_match_all(self::$regular_expression_match, $post['post_text'], $usernames); + + if (empty($usernames[1])) + { + return array(); + } + + $usernames[1] = array_unique($usernames[1]); + + $usernames = array_map('utf8_clean_string', $usernames[1]); + + $users = array(); + + $sql = 'SELECT user_id + FROM ' . USERS_TABLE . ' + WHERE ' . $this->db->sql_in_set('username_clean', $usernames) . ' + AND user_id <> ' . (int) $post['poster_id']; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $users[] = $row['user_id']; + } + $this->db->sql_freeresult($result); + + if (empty($users)) + { + return array(); + } + sort($users); + + $auth_read = $this->auth->acl_get_list($users, 'f_read', $post['forum_id']); + + if (empty($auth_read)) + { + return array(); + } + + $notify_users = $this->check_user_notification_options($auth_read[$post['forum_id']]['f_read'], $options); + + // Try to find the users who already have been notified about replies and have not read the topic since and just update their notifications + $update_notifications = array(); + $sql = 'SELECT n.* + FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . ' nt + WHERE n.notification_type_id = ' . (int) $this->notification_type_id . ' + AND n.item_parent_id = ' . (int) self::get_item_parent_id($post) . ' + AND n.notification_read = 0 + AND nt.notification_type_id = n.notification_type_id + AND nt.notification_type_enabled = 1'; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + // Do not create a new notification + unset($notify_users[$row['user_id']]); + + $notification = $this->notification_manager->get_item_type_class($this->get_type(), $row); + $sql = 'UPDATE ' . $this->notifications_table . ' + SET ' . $this->db->sql_build_array('UPDATE', $notification->add_responders($post)) . ' + WHERE notification_id = ' . $row['notification_id']; + $this->db->sql_query($sql); + } + $this->db->sql_freeresult($result); + + return $notify_users; + } + + /** + * Update a notification + * + * @param array $data Data specific for this type that will be updated + */ + public function update_notifications($post) + { + $old_notifications = array(); + $sql = 'SELECT n.user_id + FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . ' nt + WHERE n.notification_type_id = ' . (int) $this->notification_type_id . ' + AND n.item_id = ' . self::get_item_id($post) . ' + AND nt.notification_type_id = n.notification_type_id + AND nt.notification_type_enabled = 1'; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $old_notifications[] = $row['user_id']; + } + $this->db->sql_freeresult($result); + + // Find the new users to notify + $notifications = $this->find_users_for_notification($post); + + // Find the notifications we must delete + $remove_notifications = array_diff($old_notifications, array_keys($notifications)); + + // Find the notifications we must add + $add_notifications = array(); + foreach (array_diff(array_keys($notifications), $old_notifications) as $user_id) + { + $add_notifications[$user_id] = $notifications[$user_id]; + } + + // Add the necessary notifications + $this->notification_manager->add_notifications_for_users($this->get_type(), $post, $add_notifications); + + // Remove the necessary notifications + if (!empty($remove_notifications)) + { + $sql = 'DELETE FROM ' . $this->notifications_table . ' + WHERE notification_type_id = ' . (int) $this->notification_type_id . ' + AND item_id = ' . self::get_item_id($post) . ' + AND ' . $this->db->sql_in_set('user_id', $remove_notifications); + $this->db->sql_query($sql); + } + + // return true to continue with the update code in the notifications service (this will update the rest of the notifications) + return true; + } + + /** + * Get email template + * + * @return string|bool + */ + public function get_email_template() + { + return 'quote'; + } + + /** + * Get email template variables + * + * @return array + */ + public function get_email_template_variables() + { + $user_data = $this->user_loader->get_user($this->get_data('poster_id')); + + return array_merge(parent::get_email_template_variables(), array( + 'AUTHOR_NAME' => htmlspecialchars_decode($user_data['username']), + )); + } +} diff --git a/phpBB/phpbb/notification/type/report_pm.php b/phpBB/phpbb/notification/type/report_pm.php new file mode 100644 index 0000000000..3fa73bab41 --- /dev/null +++ b/phpBB/phpbb/notification/type/report_pm.php @@ -0,0 +1,229 @@ + 'report', + 'lang' => 'NOTIFICATION_TYPE_REPORT', + 'group' => 'NOTIFICATION_GROUP_MODERATION', + ); + + /** + * Get the id of the parent + * + * @param array $pm The data from the pm + */ + public static function get_item_parent_id($pm) + { + return (int) $pm['report_id']; + } + + /** + * Is this type available to the current user (defines whether or not it will be shown in the UCP Edit notification options) + * + * @return bool True/False whether or not this is available to the user + */ + public function is_available() + { + $m_approve = $this->auth->acl_getf($this->permission, true); + + return (!empty($m_approve)); + } + + + /** + * Find the users who want to receive notifications + * (copied from post_in_queue) + * + * @param array $post Data from the post + * + * @return array + */ + public function find_users_for_notification($post, $options = array()) + { + $options = array_merge(array( + 'ignore_users' => array(), + ), $options); + + // Global + $post['forum_id'] = 0; + + $auth_approve = $this->auth->acl_get_list(false, $this->permission, $post['forum_id']); + + if (empty($auth_approve)) + { + return array(); + } + + if (($key = array_search($this->user->data['user_id'], $auth_approve[$post['forum_id']][$this->permission]))) + { + unset($auth_approve[$post['forum_id']][$this->permission][$key]); + } + + return $this->check_user_notification_options($auth_approve[$post['forum_id']][$this->permission], array_merge($options, array( + 'item_type' => self::$notification_option['id'], + ))); + } + + /** + * Get email template + * + * @return string|bool + */ + public function get_email_template() + { + return 'report_pm'; + } + + /** + * Get email template variables + * + * @return array + */ + public function get_email_template_variables() + { + return array( + 'AUTHOR_NAME' => htmlspecialchars_decode($user_data['username']), + 'SUBJECT' => htmlspecialchars_decode(censor_text($this->get_data('message_subject'))), + + 'U_VIEW_REPORT' => generate_board_url() . "mcp.{$this->php_ext}?r={$this->item_parent_id}&i=pm_reports&mode=pm_report_details", + ); + } + + /** + * Get the url to this item + * + * @return string URL + */ + public function get_url() + { + return append_sid($this->phpbb_root_path . 'mcp.' . $this->php_ext, "r={$this->item_parent_id}&i=pm_reports&mode=pm_report_details"); + } + + /** + * Get the HTML formatted title of this notification + * + * @return string + */ + public function get_title() + { + $this->user->add_lang('mcp'); + + $username = $this->user_loader->get_username($this->get_data('reporter_id'), 'no_profile'); + + if ($this->get_data('report_text')) + { + return $this->user->lang( + $this->language_key, + $username, + censor_text($this->get_data('message_subject')), + $this->get_data('report_text') + ); + } + + if (isset($this->user->lang[$this->get_data('reason_title')])) + { + return $this->user->lang( + $this->language_key, + $username, + censor_text($this->get_data('message_subject')), + $this->user->lang[$this->get_data('reason_title')] + ); + } + + return $this->user->lang( + $this->language_key, + $username, + censor_text($this->get_data('message_subject')), + $this->get_data('reason_description') + ); + } + + /** + * Get the user's avatar + */ + public function get_avatar() + { + return $this->user_loader->get_avatar($this->get_data('reporter_id')); + } + + /** + * Users needed to query before this notification can be displayed + * + * @return array Array of user_ids + */ + public function users_to_query() + { + return array($this->get_data('reporter_id')); + } + + /** + * Function for preparing the data for insertion in an SQL query + * (The service handles insertion) + * + * @param array $post Data from submit_post + * @param array $pre_create_data Data from pre_create_insert_array() + * + * @return array Array of data ready to be inserted into the database + */ + public function create_insert_array($post, $pre_create_data = array()) + { + $this->set_data('reporter_id', $this->user->data['user_id']); + $this->set_data('reason_title', strtoupper($post['reason_title'])); + $this->set_data('reason_description', $post['reason_description']); + $this->set_data('report_text', $post['report_text']); + + return parent::create_insert_array($post, $pre_create_data); + } +} diff --git a/phpBB/phpbb/notification/type/report_pm_closed.php b/phpBB/phpbb/notification/type/report_pm_closed.php new file mode 100644 index 0000000000..63dfa92064 --- /dev/null +++ b/phpBB/phpbb/notification/type/report_pm_closed.php @@ -0,0 +1,155 @@ +user->data['user_id']) + { + return array(); + } + + return array($pm['reporter'] => array('')); + } + + /** + * Get email template + * + * @return string|bool + */ + public function get_email_template() + { + return false; + } + + /** + * Get email template variables + * + * @return array + */ + public function get_email_template_variables() + { + return array(); + } + + /** + * Get the url to this item + * + * @return string URL + */ + public function get_url() + { + return ''; + } + + /** + * Get the HTML formatted title of this notification + * + * @return string + */ + public function get_title() + { + $username = $this->user_loader->get_username($this->get_data('closer_id'), 'no_profile'); + + return $this->user->lang( + $this->language_key, + $username, + censor_text($this->get_data('message_subject')) + ); + } + + /** + * Get the user's avatar + */ + public function get_avatar() + { + return $this->get_user_avatar($this->get_data('closer_id')); + } + + /** + * Users needed to query before this notification can be displayed + * + * @return array Array of user_ids + */ + public function users_to_query() + { + return array($this->get_data('closer_id')); + } + + /** + * Function for preparing the data for insertion in an SQL query + * (The service handles insertion) + * + * @param array $pm PM Data + * @param array $pre_create_data Data from pre_create_insert_array() + * + * @return array Array of data ready to be inserted into the database + */ + public function create_insert_array($pm, $pre_create_data = array()) + { + $this->set_data('closer_id', $pm['closer_id']); + + $data = parent::create_insert_array($pm, $pre_create_data); + + $this->notification_time = $data['notification_time'] = time(); + + return $data; + } +} diff --git a/phpBB/phpbb/notification/type/report_post.php b/phpBB/phpbb/notification/type/report_post.php new file mode 100644 index 0000000000..de5c54a291 --- /dev/null +++ b/phpBB/phpbb/notification/type/report_post.php @@ -0,0 +1,196 @@ + 'report', + 'lang' => 'NOTIFICATION_TYPE_REPORT', + 'group' => 'NOTIFICATION_GROUP_MODERATION', + ); + + /** + * Find the users who want to receive notifications + * + * @param array $post Data from the post + * + * @return array + */ + public function find_users_for_notification($post, $options = array()) + { + $notify_users = parent::find_users_for_notification($post, $options); + + // never notify reporter + unset($notify_users[$this->user->data['user_id']]); + + return $notify_users; + } + + /** + * Get email template + * + * @return string|bool + */ + public function get_email_template() + { + return 'report_post'; + } + + /** + * Get email template variables + * + * @return array + */ + public function get_email_template_variables() + { + $board_url = generate_board_url(); + + return array( + 'POST_SUBJECT' => htmlspecialchars_decode(censor_text($this->get_data('post_subject'))), + 'TOPIC_TITLE' => htmlspecialchars_decode(censor_text($this->get_data('topic_title'))), + + 'U_VIEW_REPORT' => "{$board_url}/mcp.{$this->php_ext}?f={$this->get_data('forum_id')}&p={$this->item_id}&i=reports&mode=report_details#reports", + 'U_VIEW_POST' => "{$board_url}/viewtopic.{$this->php_ext}?p={$this->item_id}#p{$this->item_id}", + 'U_NEWEST_POST' => "{$board_url}/viewtopic.{$this->php_ext}?f={$this->get_data('forum_id')}&t={$this->item_parent_id}&view=unread#unread", + 'U_TOPIC' => "{$board_url}/viewtopic.{$this->php_ext}?f={$this->get_data('forum_id')}&t={$this->item_parent_id}", + 'U_VIEW_TOPIC' => "{$board_url}/viewtopic.{$this->php_ext}?f={$this->get_data('forum_id')}&t={$this->item_parent_id}", + 'U_FORUM' => "{$board_url}/viewforum.{$this->php_ext}?f={$this->get_data('forum_id')}", + ); + } + + /** + * Get the url to this item + * + * @return string URL + */ + public function get_url() + { + return append_sid($this->phpbb_root_path . 'mcp.' . $this->php_ext, "f={$this->get_data('forum_id')}&p={$this->item_id}&i=reports&mode=report_details#reports"); + } + + /** + * Get the HTML formatted title of this notification + * + * @return string + */ + public function get_title() + { + $this->user->add_lang('mcp'); + + $username = $this->user_loader->get_username($this->get_data('reporter_id'), 'no_profile'); + + if ($this->get_data('report_text')) + { + return $this->user->lang( + $this->language_key, + $username, + censor_text($this->get_data('post_subject')), + $this->get_data('report_text') + ); + } + + if (isset($this->user->lang[$this->get_data('reason_title')])) + { + return $this->user->lang( + $this->language_key, + $username, + censor_text($this->get_data('post_subject')), + $this->user->lang[$this->get_data('reason_title')] + ); + } + + return $this->user->lang( + $this->language_key, + $username, + censor_text($this->get_data('post_subject')), + $this->get_data('reason_description') + ); + } + + /** + * Get the user's avatar + */ + public function get_avatar() + { + return $this->user_loader->get_avatar($this->get_data('reporter_id')); + } + + /** + * Users needed to query before this notification can be displayed + * + * @return array Array of user_ids + */ + public function users_to_query() + { + return array($this->get_data('reporter_id')); + } + + /** + * Function for preparing the data for insertion in an SQL query + * (The service handles insertion) + * + * @param array $post Data from submit_post + * @param array $pre_create_data Data from pre_create_insert_array() + * + * @return array Array of data ready to be inserted into the database + */ + public function create_insert_array($post, $pre_create_data = array()) + { + $this->set_data('reporter_id', $this->user->data['user_id']); + $this->set_data('reason_title', strtoupper($post['reason_title'])); + $this->set_data('reason_description', $post['reason_description']); + $this->set_data('report_text', $post['report_text']); + + return parent::create_insert_array($post, $pre_create_data); + } +} diff --git a/phpBB/phpbb/notification/type/report_post_closed.php b/phpBB/phpbb/notification/type/report_post_closed.php new file mode 100644 index 0000000000..3916cd8db7 --- /dev/null +++ b/phpBB/phpbb/notification/type/report_post_closed.php @@ -0,0 +1,155 @@ +user->data['user_id']) + { + return array(); + } + + return array($post['reporter'] => array('')); + } + + /** + * Get email template + * + * @return string|bool + */ + public function get_email_template() + { + return false; + } + + /** + * Get email template variables + * + * @return array + */ + public function get_email_template_variables() + { + return array(); + } + + /** + * Get the url to this item + * + * @return string URL + */ + public function get_url() + { + return ''; + } + + /** + * Get the HTML formatted title of this notification + * + * @return string + */ + public function get_title() + { + $username = $this->user_loader->get_username($this->get_data('closer_id'), 'no_profile'); + + return $this->user->lang( + $this->language_key, + $username, + censor_text($this->get_data('post_subject')) + ); + } + + /** + * Get the user's avatar + */ + public function get_avatar() + { + return $this->user_loader->get_avatar($this->get_data('closer_id')); + } + + /** + * Users needed to query before this notification can be displayed + * + * @return array Array of user_ids + */ + public function users_to_query() + { + return array($this->get_data('closer_id')); + } + + /** + * Function for preparing the data for insertion in an SQL query + * (The service handles insertion) + * + * @param array $post Data from submit_post + * @param array $pre_create_data Data from pre_create_insert_array() + * + * @return array Array of data ready to be inserted into the database + */ + public function create_insert_array($post, $pre_create_data = array()) + { + $this->set_data('closer_id', $post['closer_id']); + + $data = parent::create_insert_array($post, $pre_create_data); + + $this->notification_time = $data['notification_time'] = time(); + + return $data; + } +} diff --git a/phpBB/phpbb/notification/type/topic.php b/phpBB/phpbb/notification/type/topic.php new file mode 100644 index 0000000000..22436d3fb1 --- /dev/null +++ b/phpBB/phpbb/notification/type/topic.php @@ -0,0 +1,277 @@ + 'NOTIFICATION_TYPE_TOPIC', + 'group' => 'NOTIFICATION_GROUP_POSTING', + ); + + /** + * Is available + */ + public function is_available() + { + return $this->config['allow_forum_notify']; + } + + /** + * Get the id of the item + * + * @param array $post The data from the post + */ + public static function get_item_id($post) + { + return (int) $post['topic_id']; + } + + /** + * Get the id of the parent + * + * @param array $post The data from the post + */ + public static function get_item_parent_id($post) + { + return (int) $post['forum_id']; + } + + /** + * Find the users who want to receive notifications + * + * @param array $topic Data from the topic + * + * @return array + */ + public function find_users_for_notification($topic, $options = array()) + { + $options = array_merge(array( + 'ignore_users' => array(), + ), $options); + + $users = array(); + + $sql = 'SELECT user_id + FROM ' . FORUMS_WATCH_TABLE . ' + WHERE forum_id = ' . (int) $topic['forum_id'] . ' + AND notify_status = ' . NOTIFY_YES . ' + AND user_id <> ' . (int) $topic['poster_id']; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $users[] = $row['user_id']; + } + $this->db->sql_freeresult($result); + + if (empty($users)) + { + return array(); + } + + $auth_read = $this->auth->acl_get_list($users, 'f_read', $topic['forum_id']); + + if (empty($auth_read)) + { + return array(); + } + + return $this->check_user_notification_options($auth_read[$topic['forum_id']]['f_read'], $options); + } + + /** + * Get the user's avatar + */ + public function get_avatar() + { + return $this->user_loader->get_avatar($this->get_data('poster_id')); + } + + /** + * Get the HTML formatted title of this notification + * + * @return string + */ + public function get_title() + { + if ($this->get_data('post_username')) + { + $username = $this->get_data('post_username'); + } + else + { + $username = $this->user_loader->get_username($this->get_data('poster_id'), 'no_profile'); + } + + return $this->user->lang( + $this->language_key, + $username, + censor_text($this->get_data('topic_title')), + $this->get_data('forum_name') + ); + } + + /** + * Get email template + * + * @return string|bool + */ + public function get_email_template() + { + return 'newtopic_notify'; + } + + /** + * Get email template variables + * + * @return array + */ + public function get_email_template_variables() + { + $board_url = generate_board_url(); + + if ($this->get_data('post_username')) + { + $username = $this->get_data('post_username'); + } + else + { + $username = $this->user_loader->get_username($this->get_data('poster_id'), 'username'); + } + + return array( + 'AUTHOR_NAME' => htmlspecialchars_decode($username), + 'FORUM_NAME' => htmlspecialchars_decode($this->get_data('forum_name')), + 'TOPIC_TITLE' => htmlspecialchars_decode(censor_text($this->get_data('topic_title'))), + + 'U_TOPIC' => "{$board_url}/viewtopic.{$this->php_ext}?f={$this->item_parent_id}&t={$this->item_id}", + 'U_VIEW_TOPIC' => "{$board_url}/viewtopic.{$this->php_ext}?f={$this->item_parent_id}&t={$this->item_id}", + 'U_FORUM' => "{$board_url}/viewforum.{$this->php_ext}?f={$this->item_parent_id}", + 'U_STOP_WATCHING_FORUM' => "{$board_url}/viewforum.{$this->php_ext}?uid={$this->user_id}&f={$this->item_parent_id}&unwatch=forum", + ); + } + + /** + * Get the url to this item + * + * @return string URL + */ + public function get_url() + { + return append_sid($this->phpbb_root_path . 'viewtopic.' . $this->php_ext, "f={$this->item_parent_id}&t={$this->item_id}"); + } + + /** + * Users needed to query before this notification can be displayed + * + * @return array Array of user_ids + */ + public function users_to_query() + { + return array($this->get_data('poster_id')); + } + + /** + * Pre create insert array function + * This allows you to perform certain actions, like run a query + * and load data, before create_insert_array() is run. The data + * returned from this function will be sent to create_insert_array(). + * + * @param array $post Post data from submit_post + * @param array $notify_users Notify users list + * Formated from find_users_for_notification() + * @return array Whatever you want to send to create_insert_array(). + */ + public function pre_create_insert_array($post, $notify_users) + { + if (!sizeof($notify_users)) + { + return array(); + } + + $tracking_data = array(); + $sql = 'SELECT user_id, mark_time FROM ' . TOPICS_TRACK_TABLE . ' + WHERE topic_id = ' . (int) $post['topic_id'] . ' + AND ' . $this->db->sql_in_set('user_id', array_keys($notify_users)); + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $tracking_data[$row['user_id']] = $row['mark_time']; + } + + return $tracking_data; + } + + /** + * Function for preparing the data for insertion in an SQL query + * (The service handles insertion) + * + * @param array $post Data from submit_post + * @param array $pre_create_data Data from pre_create_insert_array() + * + * @return array Array of data ready to be inserted into the database + */ + public function create_insert_array($post, $pre_create_data = array()) + { + $this->set_data('poster_id', $post['poster_id']); + + $this->set_data('topic_title', $post['topic_title']); + + $this->set_data('post_username', (($post['poster_id'] == ANONYMOUS) ? $post['post_username'] : '')); + + $this->set_data('forum_name', $post['forum_name']); + + $this->notification_time = $post['post_time']; + + // Topics can be "read" before they are public (while awaiting approval). + // Make sure that if the user has read the topic, it's marked as read in the notification + if (isset($pre_create_data[$this->user_id]) && $pre_create_data[$this->user_id] >= $this->notification_time) + { + $this->notification_read = true; + } + + return parent::create_insert_array($post, $pre_create_data); + } +} diff --git a/phpBB/phpbb/notification/type/topic_in_queue.php b/phpBB/phpbb/notification/type/topic_in_queue.php new file mode 100644 index 0000000000..f735e10c00 --- /dev/null +++ b/phpBB/phpbb/notification/type/topic_in_queue.php @@ -0,0 +1,154 @@ + 'needs_approval', + 'lang' => 'NOTIFICATION_TYPE_IN_MODERATION_QUEUE', + 'group' => 'NOTIFICATION_GROUP_MODERATION', + ); + + /** + * Permission to check for (in find_users_for_notification) + * + * @var string Permission name + */ + protected $permission = 'm_approve'; + + /** + * Is available + */ + public function is_available() + { + $has_permission = $this->auth->acl_getf($this->permission, true); + + return (!empty($has_permission)); + } + + /** + * Find the users who want to receive notifications + * + * @param array $topic Data from the topic + * + * @return array + */ + public function find_users_for_notification($topic, $options = array()) + { + $options = array_merge(array( + 'ignore_users' => array(), + ), $options); + + // 0 is for global moderator permissions + $auth_approve = $this->auth->acl_get_list(false, 'm_approve', array($topic['forum_id'], 0)); + + if (empty($auth_approve)) + { + return array(); + } + + $has_permission = array(); + + if (isset($auth_approve[$topic['forum_id']][$this->permission])) + { + $has_permission = $auth_approve[$topic['forum_id']][$this->permission]; + } + + if (isset($auth_approve[0][$this->permission])) + { + $has_permission = array_unique(array_merge($has_permission, $auth_approve[0][$this->permission])); + } + sort($has_permission); + + $auth_read = $this->auth->acl_get_list($has_permission, 'f_read', $topic['forum_id']); + if (empty($auth_read)) + { + return array(); + } + + return $this->check_user_notification_options($auth_read[$topic['forum_id']]['f_read'], array_merge($options, array( + 'item_type' => self::$notification_option['id'], + ))); + } + + /** + * Get the url to this item + * + * @return string URL + */ + public function get_url() + { + return append_sid($this->phpbb_root_path . 'mcp.' . $this->php_ext, "i=queue&mode=approve_details&f={$this->item_parent_id}&t={$this->item_id}"); + } + + /** + * Function for preparing the data for insertion in an SQL query + * (The service handles insertion) + * + * @param array $topic Data from submit_post + * @param array $pre_create_data Data from pre_create_insert_array() + * + * @return array Array of data ready to be inserted into the database + */ + public function create_insert_array($topic, $pre_create_data = array()) + { + $data = parent::create_insert_array($topic, $pre_create_data); + + $this->notification_time = $data['notification_time'] = time(); + + return $data; + } + + /** + * Get email template + * + * @return string|bool + */ + public function get_email_template() + { + return 'topic_in_queue'; + } +} diff --git a/phpBB/phpbb/php/ini.php b/phpBB/phpbb/php/ini.php new file mode 100644 index 0000000000..17e8c54a57 --- /dev/null +++ b/phpBB/phpbb/php/ini.php @@ -0,0 +1,175 @@ +get($varname); + + if ($value === false) + { + return false; + } + + return trim($value); + } + + /** + * Gets configuration option value as a boolean. + * Interprets the string value 'off' as false. + * + * @param string $varname The configuration option name. + * @return bool False if configuration option does not exist. + * False if configuration option is disabled. + * True otherwise. + */ + public function get_bool($varname) + { + $value = $this->get_string($varname); + + if (empty($value) || strtolower($value) == 'off') + { + return false; + } + + return true; + } + + /** + * Gets configuration option value as an integer. + * + * @param string $varname The configuration option name. + * @return bool|int False if configuration option does not exist, + * false if configuration option value is not numeric, + * the configuration option value (integer) otherwise. + */ + public function get_int($varname) + { + $value = $this->get_string($varname); + + if (!is_numeric($value)) + { + return false; + } + + return (int) $value; + } + + /** + * Gets configuration option value as a float. + * + * @param string $varname The configuration option name. + * @return bool|float False if configuration option does not exist, + * false if configuration option value is not numeric, + * the configuration option value (float) otherwise. + */ + public function get_float($varname) + { + $value = $this->get_string($varname); + + if (!is_numeric($value)) + { + return false; + } + + return (float) $value; + } + + /** + * Gets configuration option value in bytes. + * Converts strings like '128M' to bytes (integer or float). + * + * @param string $varname The configuration option name. + * @return bool|int|float False if configuration option does not exist, + * false if configuration option value is not well-formed, + * the configuration option value otherwise. + */ + public function get_bytes($varname) + { + $value = $this->get_string($varname); + + if ($value === false) + { + return false; + } + + if (is_numeric($value)) + { + // Already in bytes. + return phpbb_to_numeric($value); + } + else if (strlen($value) < 2) + { + // Single character. + return false; + } + else if (strlen($value) < 3 && $value[0] === '-') + { + // Two characters but the first one is a minus. + return false; + } + + $value_lower = strtolower($value); + $value_numeric = phpbb_to_numeric($value); + + switch ($value_lower[strlen($value_lower) - 1]) + { + case 'g': + $value_numeric *= 1024; + case 'm': + $value_numeric *= 1024; + case 'k': + $value_numeric *= 1024; + break; + + default: + // It's not already in bytes (and thus numeric) + // and does not carry a unit. + return false; + } + + return $value_numeric; + } +} diff --git a/phpBB/phpbb/request/deactivated_super_global.php b/phpBB/phpbb/request/deactivated_super_global.php new file mode 100644 index 0000000000..cc05847ec7 --- /dev/null +++ b/phpBB/phpbb/request/deactivated_super_global.php @@ -0,0 +1,121 @@ +request = $request; + $this->name = $name; + $this->super_global = $super_global; + } + + /** + * Calls trigger_error with the file and line number the super global was used in. + */ + private function error() + { + $file = ''; + $line = 0; + + $message = 'Illegal use of $' . $this->name . '. You must use the request class or request_var() to access input data. Found in %s on line %d. This error message was generated by deactivated_super_global.'; + + $backtrace = debug_backtrace(); + if (isset($backtrace[1])) + { + $file = $backtrace[1]['file']; + $line = $backtrace[1]['line']; + } + trigger_error(sprintf($message, $file, $line), E_USER_ERROR); + } + + /** + * Redirects isset to the correct request class call. + * + * @param string $offset The key of the super global being accessed. + * + * @return bool Whether the key on the super global exists. + */ + public function offsetExists($offset) + { + return $this->request->is_set($offset, $this->super_global); + } + + /**#@+ + * Part of the ArrayAccess implementation, will always result in a FATAL error. + */ + public function offsetGet($offset) + { + $this->error(); + } + + public function offsetSet($offset, $value) + { + $this->error(); + } + + public function offsetUnset($offset) + { + $this->error(); + } + /**#@-*/ + + /** + * Part of the Countable implementation, will always result in a FATAL error + */ + public function count() + { + $this->error(); + } + + /** + * Part of the Traversable/IteratorAggregate implementation, will always result in a FATAL error + */ + public function getIterator() + { + $this->error(); + } +} + diff --git a/phpBB/phpbb/request/interface.php b/phpBB/phpbb/request/interface.php new file mode 100644 index 0000000000..741db35917 --- /dev/null +++ b/phpBB/phpbb/request/interface.php @@ -0,0 +1,139 @@ + "a") + * then specifying array("var", 1) as the name will return "a". + * @param mixed $default A default value that is returned if the variable was not set. + * This function will always return a value of the same type as the default. + * @param bool $multibyte If $default is a string this paramater has to be true if the variable may contain any UTF-8 characters + * Default is false, causing all bytes outside the ASCII range (0-127) to be replaced with question marks + * @param phpbb_request_interface::POST|GET|REQUEST|COOKIE $super_global + * Specifies which super global should be used + * + * @return mixed The value of $_REQUEST[$var_name] run through {@link set_var set_var} to ensure that the type is the + * the same as that of $default. If the variable is not set $default is returned. + */ + public function variable($var_name, $default, $multibyte = false, $super_global = phpbb_request_interface::REQUEST); + + /** + * Shortcut method to retrieve SERVER variables. + * + * @param string|array $var_name See phpbb_request_interface::variable + * @param mixed $default See phpbb_request_interface::variable + * + * @return mixed The server variable value. + */ + public function server($var_name, $default = ''); + + /** + * Shortcut method to retrieve the value of client HTTP headers. + * + * @param string|array $header_name The name of the header to retrieve. + * @param mixed $default See phpbb_request_interface::variable + * + * @return mixed The header value. + */ + public function header($var_name, $default = ''); + + /** + * Checks whether a certain variable was sent via POST. + * To make sure that a request was sent using POST you should call this function + * on at least one variable. + * + * @param string $name The name of the form variable which should have a + * _p suffix to indicate the check in the code that creates the form too. + * + * @return bool True if the variable was set in a POST request, false otherwise. + */ + public function is_set_post($name); + + /** + * Checks whether a certain variable is set in one of the super global + * arrays. + * + * @param string $var Name of the variable + * @param phpbb_request_interface::POST|GET|REQUEST|COOKIE $super_global + * Specifies the super global which shall be checked + * + * @return bool True if the variable was sent as input + */ + public function is_set($var, $super_global = phpbb_request_interface::REQUEST); + + /** + * Checks whether the current request is an AJAX request (XMLHttpRequest) + * + * @return bool True if the current request is an ajax request + */ + public function is_ajax(); + + /** + * Checks if the current request is happening over HTTPS. + * + * @return bool True if the request is secure. + */ + public function is_secure(); + + /** + * Returns all variable names for a given super global + * + * @param phpbb_request_interface::POST|GET|REQUEST|COOKIE $super_global + * The super global from which names shall be taken + * + * @return array All variable names that are set for the super global. + * Pay attention when using these, they are unsanitised! + */ + public function variable_names($super_global = phpbb_request_interface::REQUEST); +} diff --git a/phpBB/phpbb/request/request.php b/phpBB/phpbb/request/request.php new file mode 100644 index 0000000000..ae3c526d89 --- /dev/null +++ b/phpBB/phpbb/request/request.php @@ -0,0 +1,415 @@ + '_POST', + phpbb_request_interface::GET => '_GET', + phpbb_request_interface::REQUEST => '_REQUEST', + phpbb_request_interface::COOKIE => '_COOKIE', + phpbb_request_interface::SERVER => '_SERVER', + phpbb_request_interface::FILES => '_FILES', + ); + + /** + * @var array Stores original contents of $_REQUEST array. + */ + protected $original_request = null; + + /** + * @var + */ + protected $super_globals_disabled = false; + + /** + * @var array An associative array that has the value of super global constants as keys and holds their data as values. + */ + protected $input; + + /** + * @var phpbb_request_type_cast_helper_interface An instance of a type cast helper providing convenience methods for type conversions. + */ + protected $type_cast_helper; + + /** + * Initialises the request class, that means it stores all input data in {@link $input input} + * and then calls {@link phpbb_request_deactivated_super_global phpbb_request_deactivated_super_global} + */ + public function __construct(phpbb_request_type_cast_helper_interface $type_cast_helper = null, $disable_super_globals = true) + { + if ($type_cast_helper) + { + $this->type_cast_helper = $type_cast_helper; + } + else + { + $this->type_cast_helper = new phpbb_request_type_cast_helper(); + } + + foreach ($this->super_globals as $const => $super_global) + { + $this->input[$const] = isset($GLOBALS[$super_global]) ? $GLOBALS[$super_global] : array(); + } + + // simulate request_order = GP + $this->original_request = $this->input[phpbb_request_interface::REQUEST]; + $this->input[phpbb_request_interface::REQUEST] = $this->input[phpbb_request_interface::POST] + $this->input[phpbb_request_interface::GET]; + + if ($disable_super_globals) + { + $this->disable_super_globals(); + } + } + + /** + * Getter for $super_globals_disabled + * + * @return bool Whether super globals are disabled or not. + */ + public function super_globals_disabled() + { + return $this->super_globals_disabled; + } + + /** + * Disables access of super globals specified in $super_globals. + * This is achieved by overwriting the super globals with instances of {@link phpbb_request_deactivated_super_global phpbb_request_deactivated_super_global} + */ + public function disable_super_globals() + { + if (!$this->super_globals_disabled) + { + foreach ($this->super_globals as $const => $super_global) + { + unset($GLOBALS[$super_global]); + $GLOBALS[$super_global] = new phpbb_request_deactivated_super_global($this, $super_global, $const); + } + + $this->super_globals_disabled = true; + } + } + + /** + * Enables access of super globals specified in $super_globals if they were disabled by {@link disable_super_globals disable_super_globals}. + * This is achieved by making the super globals point to the data stored within this class in {@link $input input}. + */ + public function enable_super_globals() + { + if ($this->super_globals_disabled) + { + foreach ($this->super_globals as $const => $super_global) + { + $GLOBALS[$super_global] = $this->input[$const]; + } + + $GLOBALS['_REQUEST'] = $this->original_request; + + $this->super_globals_disabled = false; + } + } + + /** + * This function allows overwriting or setting a value in one of the super global arrays. + * + * Changes which are performed on the super globals directly will not have any effect on the results of + * other methods this class provides. Using this function should be avoided if possible! It will + * consume twice the the amount of memory of the value + * + * @param string $var_name The name of the variable that shall be overwritten + * @param mixed $value The value which the variable shall contain. + * If this is null the variable will be unset. + * @param phpbb_request_interface::POST|GET|REQUEST|COOKIE $super_global + * Specifies which super global shall be changed + */ + public function overwrite($var_name, $value, $super_global = phpbb_request_interface::REQUEST) + { + if (!isset($this->super_globals[$super_global])) + { + return; + } + + $this->type_cast_helper->add_magic_quotes($value); + + // setting to null means unsetting + if ($value === null) + { + unset($this->input[$super_global][$var_name]); + if (!$this->super_globals_disabled()) + { + unset($GLOBALS[$this->super_globals[$super_global]][$var_name]); + } + } + else + { + $this->input[$super_global][$var_name] = $value; + if (!$this->super_globals_disabled()) + { + $GLOBALS[$this->super_globals[$super_global]][$var_name] = $value; + } + } + + if (!$this->super_globals_disabled()) + { + unset($GLOBALS[$this->super_globals[$super_global]][$var_name]); + $GLOBALS[$this->super_globals[$super_global]][$var_name] = $value; + } + } + + /** + * Central type safe input handling function. + * All variables in GET or POST requests should be retrieved through this function to maximise security. + * + * @param string|array $var_name The form variable's name from which data shall be retrieved. + * If the value is an array this may be an array of indizes which will give + * direct access to a value at any depth. E.g. if the value of "var" is array(1 => "a") + * then specifying array("var", 1) as the name will return "a". + * @param mixed $default A default value that is returned if the variable was not set. + * This function will always return a value of the same type as the default. + * @param bool $multibyte If $default is a string this paramater has to be true if the variable may contain any UTF-8 characters + * Default is false, causing all bytes outside the ASCII range (0-127) to be replaced with question marks + * @param phpbb_request_interface::POST|GET|REQUEST|COOKIE $super_global + * Specifies which super global should be used + * + * @return mixed The value of $_REQUEST[$var_name] run through {@link set_var set_var} to ensure that the type is the + * the same as that of $default. If the variable is not set $default is returned. + */ + public function variable($var_name, $default, $multibyte = false, $super_global = phpbb_request_interface::REQUEST) + { + return $this->_variable($var_name, $default, $multibyte, $super_global, true); + } + + /** + * Get a variable, but without trimming strings. + * Same functionality as variable(), except does not run trim() on strings. + * This method should be used when handling passwords. + * + * @param string|array $var_name The form variable's name from which data shall be retrieved. + * If the value is an array this may be an array of indizes which will give + * direct access to a value at any depth. E.g. if the value of "var" is array(1 => "a") + * then specifying array("var", 1) as the name will return "a". + * @param mixed $default A default value that is returned if the variable was not set. + * This function will always return a value of the same type as the default. + * @param bool $multibyte If $default is a string this paramater has to be true if the variable may contain any UTF-8 characters + * Default is false, causing all bytes outside the ASCII range (0-127) to be replaced with question marks + * @param phpbb_request_interface::POST|GET|REQUEST|COOKIE $super_global + * Specifies which super global should be used + * + * @return mixed The value of $_REQUEST[$var_name] run through {@link set_var set_var} to ensure that the type is the + * the same as that of $default. If the variable is not set $default is returned. + */ + public function untrimmed_variable($var_name, $default, $multibyte, $super_global = phpbb_request_interface::REQUEST) + { + return $this->_variable($var_name, $default, $multibyte, $super_global, false); + } + + /** + * Shortcut method to retrieve SERVER variables. + * + * Also fall back to getenv(), some CGI setups may need it (probably not, but + * whatever). + * + * @param string|array $var_name See phpbb_request_interface::variable + * @param mixed $Default See phpbb_request_interface::variable + * + * @return mixed The server variable value. + */ + public function server($var_name, $default = '') + { + $multibyte = true; + + if ($this->is_set($var_name, phpbb_request_interface::SERVER)) + { + return $this->variable($var_name, $default, $multibyte, phpbb_request_interface::SERVER); + } + else + { + $var = getenv($var_name); + $this->type_cast_helper->recursive_set_var($var, $default, $multibyte); + return $var; + } + } + + /** + * Shortcut method to retrieve the value of client HTTP headers. + * + * @param string|array $header_name The name of the header to retrieve. + * @param mixed $default See phpbb_request_interface::variable + * + * @return mixed The header value. + */ + public function header($header_name, $default = '') + { + $var_name = 'HTTP_' . str_replace('-', '_', strtoupper($header_name)); + return $this->server($var_name, $default); + } + + /** + * Shortcut method to retrieve $_FILES variables + * + * @param string $form_name The name of the file input form element + * + * @return array The uploaded file's information or an empty array if the + * variable does not exist in _FILES. + */ + public function file($form_name) + { + return $this->variable($form_name, array('name' => 'none'), false, phpbb_request_interface::FILES); + } + + /** + * Checks whether a certain variable was sent via POST. + * To make sure that a request was sent using POST you should call this function + * on at least one variable. + * + * @param string $name The name of the form variable which should have a + * _p suffix to indicate the check in the code that creates the form too. + * + * @return bool True if the variable was set in a POST request, false otherwise. + */ + public function is_set_post($name) + { + return $this->is_set($name, phpbb_request_interface::POST); + } + + /** + * Checks whether a certain variable is set in one of the super global + * arrays. + * + * @param string $var Name of the variable + * @param phpbb_request_interface::POST|GET|REQUEST|COOKIE $super_global + * Specifies the super global which shall be checked + * + * @return bool True if the variable was sent as input + */ + public function is_set($var, $super_global = phpbb_request_interface::REQUEST) + { + return isset($this->input[$super_global][$var]); + } + + /** + * Checks whether the current request is an AJAX request (XMLHttpRequest) + * + * @return bool True if the current request is an ajax request + */ + public function is_ajax() + { + return $this->header('X-Requested-With') == 'XMLHttpRequest'; + } + + /** + * Checks if the current request is happening over HTTPS. + * + * @return bool True if the request is secure. + */ + public function is_secure() + { + return $this->server('HTTPS') == 'on'; + } + + /** + * Returns all variable names for a given super global + * + * @param phpbb_request_interface::POST|GET|REQUEST|COOKIE $super_global + * The super global from which names shall be taken + * + * @return array All variable names that are set for the super global. + * Pay attention when using these, they are unsanitised! + */ + public function variable_names($super_global = phpbb_request_interface::REQUEST) + { + if (!isset($this->input[$super_global])) + { + return array(); + } + + return array_keys($this->input[$super_global]); + } + + /** + * Helper function used by variable() and untrimmed_variable(). + * + * @param string|array $var_name The form variable's name from which data shall be retrieved. + * If the value is an array this may be an array of indizes which will give + * direct access to a value at any depth. E.g. if the value of "var" is array(1 => "a") + * then specifying array("var", 1) as the name will return "a". + * @param mixed $default A default value that is returned if the variable was not set. + * This function will always return a value of the same type as the default. + * @param bool $multibyte If $default is a string this paramater has to be true if the variable may contain any UTF-8 characters + * Default is false, causing all bytes outside the ASCII range (0-127) to be replaced with question marks + * @param phpbb_request_interface::POST|GET|REQUEST|COOKIE $super_global + * Specifies which super global should be used + * @param bool $trim Indicates whether trim() should be applied to string values. + * + * @return mixed The value of $_REQUEST[$var_name] run through {@link set_var set_var} to ensure that the type is the + * the same as that of $default. If the variable is not set $default is returned. + */ + protected function _variable($var_name, $default, $multibyte = false, $super_global = phpbb_request_interface::REQUEST, $trim = true) + { + $path = false; + + // deep direct access to multi dimensional arrays + if (is_array($var_name)) + { + $path = $var_name; + // make sure at least the variable name is specified + if (empty($path)) + { + return (is_array($default)) ? array() : $default; + } + // the variable name is the first element on the path + $var_name = array_shift($path); + } + + if (!isset($this->input[$super_global][$var_name])) + { + return (is_array($default)) ? array() : $default; + } + $var = $this->input[$super_global][$var_name]; + + if ($path) + { + // walk through the array structure and find the element we are looking for + foreach ($path as $key) + { + if (is_array($var) && isset($var[$key])) + { + $var = $var[$key]; + } + else + { + return (is_array($default)) ? array() : $default; + } + } + } + + $this->type_cast_helper->recursive_set_var($var, $default, $multibyte, $trim); + + return $var; + } +} diff --git a/phpBB/phpbb/request/type_cast_helper.php b/phpBB/phpbb/request/type_cast_helper.php new file mode 100644 index 0000000000..1a5274ed14 --- /dev/null +++ b/phpBB/phpbb/request/type_cast_helper.php @@ -0,0 +1,194 @@ +=')) + { + $this->strip = false; + } + else + { + $this->strip = (@get_magic_quotes_gpc()) ? true : false; + } + } + + /** + * Recursively applies addslashes to a variable. + * + * @param mixed &$var Variable passed by reference to which slashes will be added. + */ + public function addslashes_recursively(&$var) + { + if (is_string($var)) + { + $var = addslashes($var); + } + else if (is_array($var)) + { + $var_copy = $var; + $var = array(); + foreach ($var_copy as $key => $value) + { + if (is_string($key)) + { + $key = addslashes($key); + } + $var[$key] = $value; + + $this->addslashes_recursively($var[$key]); + } + } + } + + /** + * Recursively applies addslashes to a variable if magic quotes are turned on. + * + * @param mixed &$var Variable passed by reference to which slashes will be added. + */ + public function add_magic_quotes(&$var) + { + if ($this->strip) + { + $this->addslashes_recursively($var); + } + } + + /** + * Set variable $result to a particular type. + * + * @param mixed &$result The variable to fill + * @param mixed $var The contents to fill with + * @param mixed $type The variable type. Will be used with {@link settype()} + * @param bool $multibyte Indicates whether string values may contain UTF-8 characters. + * Default is false, causing all bytes outside the ASCII range (0-127) to be replaced with question marks. + * @param bool $trim Indicates whether trim() should be applied to string values. + * Default is true. + */ + public function set_var(&$result, $var, $type, $multibyte = false, $trim = true) + { + settype($var, $type); + $result = $var; + + if ($type == 'string') + { + $result = str_replace(array("\r\n", "\r", "\0"), array("\n", "\n", ''), $result); + + if ($trim) + { + $result = trim($result); + } + + $result = htmlspecialchars($result, ENT_COMPAT, 'UTF-8'); + + if ($multibyte) + { + $result = utf8_normalize_nfc($result); + } + + if (!empty($result)) + { + // Make sure multibyte characters are wellformed + if ($multibyte) + { + if (!preg_match('/^./u', $result)) + { + $result = ''; + } + } + else + { + // no multibyte, allow only ASCII (0-127) + $result = preg_replace('/[\x80-\xFF]/', '?', $result); + } + } + + $result = ($this->strip) ? stripslashes($result) : $result; + } + } + + /** + * Recursively sets a variable to a given type using {@link set_var set_var} + * + * @param string $var The value which shall be sanitised (passed by reference). + * @param mixed $default Specifies the type $var shall have. + * If it is an array and $var is not one, then an empty array is returned. + * Otherwise var is cast to the same type, and if $default is an array all + * keys and values are cast recursively using this function too. + * @param bool $multibyte Indicates whether string keys and values may contain UTF-8 characters. + * Default is false, causing all bytes outside the ASCII range (0-127) to + * be replaced with question marks. + * @param bool $trim Indicates whether trim() should be applied to string values. + * Default is true. + */ + public function recursive_set_var(&$var, $default, $multibyte, $trim = true) + { + if (is_array($var) !== is_array($default)) + { + $var = (is_array($default)) ? array() : $default; + return; + } + + if (!is_array($default)) + { + $type = gettype($default); + $this->set_var($var, $var, $type, $multibyte, $trim); + } + else + { + // make sure there is at least one key/value pair to use get the + // types from + if (empty($default)) + { + $var = array(); + return; + } + + list($default_key, $default_value) = each($default); + $value_type = gettype($default_value); + $key_type = gettype($default_key); + + $_var = $var; + $var = array(); + + foreach ($_var as $k => $v) + { + $this->set_var($k, $k, $key_type, $multibyte); + + $this->recursive_set_var($v, $default_value, $multibyte, $trim); + $var[$k] = $v; + } + } + } +} diff --git a/phpBB/phpbb/request/type_cast_helper_interface.php b/phpBB/phpbb/request/type_cast_helper_interface.php new file mode 100644 index 0000000000..3920d16fc7 --- /dev/null +++ b/phpBB/phpbb/request/type_cast_helper_interface.php @@ -0,0 +1,63 @@ +ignore_words)) + { + global $user, $phpEx; + + $words = array(); + + if (file_exists("{$user->lang_path}{$user->lang_name}/search_ignore_words.$phpEx")) + { + // include the file containing ignore words + include("{$user->lang_path}{$user->lang_name}/search_ignore_words.$phpEx"); + } + + $this->ignore_words = $words; + unset($words); + } + } + + /** + * Stores a list of synonyms that should be replaced in $this->match_synonym and $this->replace_synonym and caches them + */ + function get_synonyms() + { + if (!sizeof($this->match_synonym)) + { + global $user, $phpEx; + + $synonyms = array(); + + if (file_exists("{$user->lang_path}{$user->lang_name}/search_synonyms.$phpEx")) + { + // include the file containing synonyms + include("{$user->lang_path}{$user->lang_name}/search_synonyms.$phpEx"); + } + + $this->match_synonym = array_keys($synonyms); + $this->replace_synonym = array_values($synonyms); + + unset($synonyms); + } + } + + /** + * Retrieves cached search results + * + * @param int &$result_count will contain the number of all results for the search (not only for the current page) + * @param array &$id_ary is filled with the ids belonging to the requested page that are stored in the cache + * + * @return int SEARCH_RESULT_NOT_IN_CACHE or SEARCH_RESULT_IN_CACHE or SEARCH_RESULT_INCOMPLETE + */ + function obtain_ids($search_key, &$result_count, &$id_ary, &$start, $per_page, $sort_dir) + { + global $cache; + + if (!($stored_ids = $cache->get('_search_results_' . $search_key))) + { + // no search results cached for this search_key + return SEARCH_RESULT_NOT_IN_CACHE; + } + else + { + $result_count = $stored_ids[-1]; + $reverse_ids = ($stored_ids[-2] != $sort_dir) ? true : false; + $complete = true; + + // Change start parameter in case out of bounds + if ($result_count) + { + if ($start < 0) + { + $start = 0; + } + else if ($start >= $result_count) + { + $start = floor(($result_count - 1) / $per_page) * $per_page; + } + } + + // change the start to the actual end of the current request if the sort direction differs + // from the dirction in the cache and reverse the ids later + if ($reverse_ids) + { + $start = $result_count - $start - $per_page; + + // the user requested a page past the last index + if ($start < 0) + { + return SEARCH_RESULT_NOT_IN_CACHE; + } + } + + for ($i = $start, $n = $start + $per_page; ($i < $n) && ($i < $result_count); $i++) + { + if (!isset($stored_ids[$i])) + { + $complete = false; + } + else + { + $id_ary[] = $stored_ids[$i]; + } + } + unset($stored_ids); + + if ($reverse_ids) + { + $id_ary = array_reverse($id_ary); + } + + if (!$complete) + { + return SEARCH_RESULT_INCOMPLETE; + } + return SEARCH_RESULT_IN_CACHE; + } + } + + /** + * Caches post/topic ids + * + * @param array &$id_ary contains a list of post or topic ids that shall be cached, the first element + * must have the absolute index $start in the result set. + */ + function save_ids($search_key, $keywords, $author_ary, $result_count, &$id_ary, $start, $sort_dir) + { + global $cache, $config, $db, $user; + + $length = min(sizeof($id_ary), $config['search_block_size']); + + // nothing to cache so exit + if (!$length) + { + return; + } + + $store_ids = array_slice($id_ary, 0, $length); + + // create a new resultset if there is none for this search_key yet + // or add the ids to the existing resultset + if (!($store = $cache->get('_search_results_' . $search_key))) + { + // add the current keywords to the recent searches in the cache which are listed on the search page + if (!empty($keywords) || sizeof($author_ary)) + { + $sql = 'SELECT search_time + FROM ' . SEARCH_RESULTS_TABLE . ' + WHERE search_key = \'' . $db->sql_escape($search_key) . '\''; + $result = $db->sql_query($sql); + + if (!$db->sql_fetchrow($result)) + { + $sql_ary = array( + 'search_key' => $search_key, + 'search_time' => time(), + 'search_keywords' => $keywords, + 'search_authors' => ' ' . implode(' ', $author_ary) . ' ' + ); + + $sql = 'INSERT INTO ' . SEARCH_RESULTS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary); + $db->sql_query($sql); + } + $db->sql_freeresult($result); + } + + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_last_search = ' . time() . ' + WHERE user_id = ' . $user->data['user_id']; + $db->sql_query($sql); + + $store = array(-1 => $result_count, -2 => $sort_dir); + $id_range = range($start, $start + $length - 1); + } + else + { + // we use one set of results for both sort directions so we have to calculate the indizes + // for the reversed array and we also have to reverse the ids themselves + if ($store[-2] != $sort_dir) + { + $store_ids = array_reverse($store_ids); + $id_range = range($store[-1] - $start - $length, $store[-1] - $start - 1); + } + else + { + $id_range = range($start, $start + $length - 1); + } + } + + $store_ids = array_combine($id_range, $store_ids); + + // append the ids + if (is_array($store_ids)) + { + $store += $store_ids; + + // if the cache is too big + if (sizeof($store) - 2 > 20 * $config['search_block_size']) + { + // remove everything in front of two blocks in front of the current start index + for ($i = 0, $n = $id_range[0] - 2 * $config['search_block_size']; $i < $n; $i++) + { + if (isset($store[$i])) + { + unset($store[$i]); + } + } + + // remove everything after two blocks after the current stop index + end($id_range); + for ($i = $store[-1] - 1, $n = current($id_range) + 2 * $config['search_block_size']; $i > $n; $i--) + { + if (isset($store[$i])) + { + unset($store[$i]); + } + } + } + $cache->put('_search_results_' . $search_key, $store, $config['search_store_results']); + + $sql = 'UPDATE ' . SEARCH_RESULTS_TABLE . ' + SET search_time = ' . time() . ' + WHERE search_key = \'' . $db->sql_escape($search_key) . '\''; + $db->sql_query($sql); + } + + unset($store); + unset($store_ids); + unset($id_range); + } + + /** + * Removes old entries from the search results table and removes searches with keywords that contain a word in $words. + */ + function destroy_cache($words, $authors = false) + { + global $db, $cache, $config; + + // clear all searches that searched for the specified words + if (sizeof($words)) + { + $sql_where = ''; + foreach ($words as $word) + { + $sql_where .= " OR search_keywords " . $db->sql_like_expression($db->any_char . $word . $db->any_char); + } + + $sql = 'SELECT search_key + FROM ' . SEARCH_RESULTS_TABLE . " + WHERE search_keywords LIKE '%*%' $sql_where"; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $cache->destroy('_search_results_' . $row['search_key']); + } + $db->sql_freeresult($result); + } + + // clear all searches that searched for the specified authors + if (is_array($authors) && sizeof($authors)) + { + $sql_where = ''; + foreach ($authors as $author) + { + $sql_where .= (($sql_where) ? ' OR ' : '') . 'search_authors ' . $db->sql_like_expression($db->any_char . ' ' . (int) $author . ' ' . $db->any_char); + } + + $sql = 'SELECT search_key + FROM ' . SEARCH_RESULTS_TABLE . " + WHERE $sql_where"; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $cache->destroy('_search_results_' . $row['search_key']); + } + $db->sql_freeresult($result); + } + + $sql = 'DELETE + FROM ' . SEARCH_RESULTS_TABLE . ' + WHERE search_time < ' . (time() - $config['search_store_results']); + $db->sql_query($sql); + } +} diff --git a/phpBB/phpbb/search/fulltext_mysql.php b/phpBB/phpbb/search/fulltext_mysql.php new file mode 100644 index 0000000000..7dc4da8ffe --- /dev/null +++ b/phpBB/phpbb/search/fulltext_mysql.php @@ -0,0 +1,948 @@ +config = $config; + $this->db = $db; + $this->user = $user; + + $this->word_length = array('min' => $this->config['fulltext_mysql_min_word_len'], 'max' => $this->config['fulltext_mysql_max_word_len']); + + /** + * Load the UTF tools + */ + if (!function_exists('utf8_strlen')) + { + include($phpbb_root_path . 'includes/utf/utf_tools.' . $phpEx); + } + + $error = false; + } + + /** + * Returns the name of this search backend to be displayed to administrators + * + * @return string Name + */ + public function get_name() + { + return 'MySQL Fulltext'; + } + + /** + * Returns the search_query + * + * @return string search query + */ + public function get_search_query() + { + return $this->search_query; + } + + /** + * Returns the common_words array + * + * @return array common words that are ignored by search backend + */ + public function get_common_words() + { + return $this->common_words; + } + + /** + * Returns the word_length array + * + * @return array min and max word length for searching + */ + public function get_word_length() + { + return $this->word_length; + } + + /** + * Checks for correct MySQL version and stores min/max word length in the config + * + * @return string|bool Language key of the error/incompatiblity occurred + */ + public function init() + { + if ($this->db->sql_layer != 'mysql4' && $this->db->sql_layer != 'mysqli') + { + return $this->user->lang['FULLTEXT_MYSQL_INCOMPATIBLE_DATABASE']; + } + + $result = $this->db->sql_query('SHOW TABLE STATUS LIKE \'' . POSTS_TABLE . '\''); + $info = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + $engine = ''; + if (isset($info['Engine'])) + { + $engine = $info['Engine']; + } + else if (isset($info['Type'])) + { + $engine = $info['Type']; + } + + $fulltext_supported = + $engine === 'MyISAM' || + // FULLTEXT is supported on InnoDB since MySQL 5.6.4 according to + // http://dev.mysql.com/doc/refman/5.6/en/innodb-storage-engine.html + $engine === 'InnoDB' && + phpbb_version_compare($this->db->sql_server_info(true), '5.6.4', '>='); + + if (!$fulltext_supported) + { + return $this->user->lang['FULLTEXT_MYSQL_NOT_SUPPORTED']; + } + + $sql = 'SHOW VARIABLES + LIKE \'ft\_%\''; + $result = $this->db->sql_query($sql); + + $mysql_info = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $mysql_info[$row['Variable_name']] = $row['Value']; + } + $this->db->sql_freeresult($result); + + set_config('fulltext_mysql_max_word_len', $mysql_info['ft_max_word_len']); + set_config('fulltext_mysql_min_word_len', $mysql_info['ft_min_word_len']); + + return false; + } + + /** + * Splits keywords entered by a user into an array of words stored in $this->split_words + * Stores the tidied search query in $this->search_query + * + * @param string &$keywords Contains the keyword as entered by the user + * @param string $terms is either 'all' or 'any' + * @return bool false if no valid keywords were found and otherwise true + */ + public function split_keywords(&$keywords, $terms) + { + if ($terms == 'all') + { + $match = array('#\sand\s#iu', '#\sor\s#iu', '#\snot\s#iu', '#(^|\s)\+#', '#(^|\s)-#', '#(^|\s)\|#'); + $replace = array(' +', ' |', ' -', ' +', ' -', ' |'); + + $keywords = preg_replace($match, $replace, $keywords); + } + + // Filter out as above + $split_keywords = preg_replace("#[\n\r\t]+#", ' ', trim(htmlspecialchars_decode($keywords))); + + // Split words + $split_keywords = preg_replace('#([^\p{L}\p{N}\'*"()])#u', '$1$1', str_replace('\'\'', '\' \'', trim($split_keywords))); + $matches = array(); + preg_match_all('#(?:[^\p{L}\p{N}*"()]|^)([+\-|]?(?:[\p{L}\p{N}*"()]+\'?)*[\p{L}\p{N}*"()])(?:[^\p{L}\p{N}*"()]|$)#u', $split_keywords, $matches); + $this->split_words = $matches[1]; + + // We limit the number of allowed keywords to minimize load on the database + if ($this->config['max_num_search_keywords'] && sizeof($this->split_words) > $this->config['max_num_search_keywords']) + { + trigger_error($this->user->lang('MAX_NUM_SEARCH_KEYWORDS_REFINE', $this->config['max_num_search_keywords'], sizeof($this->split_words))); + } + + // to allow phrase search, we need to concatenate quoted words + $tmp_split_words = array(); + $phrase = ''; + foreach ($this->split_words as $word) + { + if ($phrase) + { + $phrase .= ' ' . $word; + if (strpos($word, '"') !== false && substr_count($word, '"') % 2 == 1) + { + $tmp_split_words[] = $phrase; + $phrase = ''; + } + } + else if (strpos($word, '"') !== false && substr_count($word, '"') % 2 == 1) + { + $phrase = $word; + } + else + { + $tmp_split_words[] = $word; + } + } + if ($phrase) + { + $tmp_split_words[] = $phrase; + } + + $this->split_words = $tmp_split_words; + + unset($tmp_split_words); + unset($phrase); + + foreach ($this->split_words as $i => $word) + { + $clean_word = preg_replace('#^[+\-|"]#', '', $word); + + // check word length + $clean_len = utf8_strlen(str_replace('*', '', $clean_word)); + if (($clean_len < $this->config['fulltext_mysql_min_word_len']) || ($clean_len > $this->config['fulltext_mysql_max_word_len'])) + { + $this->common_words[] = $word; + unset($this->split_words[$i]); + } + } + + if ($terms == 'any') + { + $this->search_query = ''; + foreach ($this->split_words as $word) + { + if ((strpos($word, '+') === 0) || (strpos($word, '-') === 0) || (strpos($word, '|') === 0)) + { + $word = substr($word, 1); + } + $this->search_query .= $word . ' '; + } + } + else + { + $this->search_query = ''; + foreach ($this->split_words as $word) + { + if ((strpos($word, '+') === 0) || (strpos($word, '-') === 0)) + { + $this->search_query .= $word . ' '; + } + else if (strpos($word, '|') === 0) + { + $this->search_query .= substr($word, 1) . ' '; + } + else + { + $this->search_query .= '+' . $word . ' '; + } + } + } + + $this->search_query = utf8_htmlspecialchars($this->search_query); + + if ($this->search_query) + { + $this->split_words = array_values($this->split_words); + sort($this->split_words); + return true; + } + return false; + } + + /** + * Turns text into an array of words + * @param string $text contains post text/subject + */ + public function split_message($text) + { + // Split words + $text = preg_replace('#([^\p{L}\p{N}\'*])#u', '$1$1', str_replace('\'\'', '\' \'', trim($text))); + $matches = array(); + preg_match_all('#(?:[^\p{L}\p{N}*]|^)([+\-|]?(?:[\p{L}\p{N}*]+\'?)*[\p{L}\p{N}*])(?:[^\p{L}\p{N}*]|$)#u', $text, $matches); + $text = $matches[1]; + + // remove too short or too long words + $text = array_values($text); + for ($i = 0, $n = sizeof($text); $i < $n; $i++) + { + $text[$i] = trim($text[$i]); + if (utf8_strlen($text[$i]) < $this->config['fulltext_mysql_min_word_len'] || utf8_strlen($text[$i]) > $this->config['fulltext_mysql_max_word_len']) + { + unset($text[$i]); + } + } + + return array_values($text); + } + + /** + * Performs a search on keywords depending on display specific params. You have to run split_keywords() first + * + * @param string $type contains either posts or topics depending on what should be searched for + * @param string $fields contains either titleonly (topic titles should be searched), msgonly (only message bodies should be searched), firstpost (only subject and body of the first post should be searched) or all (all post bodies and subjects should be searched) + * @param string $terms is either 'all' (use query as entered, words without prefix should default to "have to be in field") or 'any' (ignore search query parts and just return all posts that contain any of the specified words) + * @param array $sort_by_sql contains SQL code for the ORDER BY part of a query + * @param string $sort_key is the key of $sort_by_sql for the selected sorting + * @param string $sort_dir is either a or d representing ASC and DESC + * @param string $sort_days specifies the maximum amount of days a post may be old + * @param array $ex_fid_ary specifies an array of forum ids which should not be searched + * @param string $post_visibility specifies which types of posts the user can view in which forums + * @param int $topic_id is set to 0 or a topic id, if it is not 0 then only posts in this topic should be searched + * @param array $author_ary an array of author ids if the author should be ignored during the search the array is empty + * @param string $author_name specifies the author match, when ANONYMOUS is also a search-match + * @param array &$id_ary passed by reference, to be filled with ids for the page specified by $start and $per_page, should be ordered + * @param int $start indicates the first index of the page + * @param int $per_page number of ids each page is supposed to contain + * @return boolean|int total number of results + */ + public function keyword_search($type, $fields, $terms, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $post_visibility, $topic_id, $author_ary, $author_name, &$id_ary, &$start, $per_page) + { + // No keywords? No posts + if (!$this->search_query) + { + return false; + } + + // generate a search_key from all the options to identify the results + $search_key = md5(implode('#', array( + implode(', ', $this->split_words), + $type, + $fields, + $terms, + $sort_days, + $sort_key, + $topic_id, + implode(',', $ex_fid_ary), + $post_visibility, + implode(',', $author_ary) + ))); + + if ($start < 0) + { + $start = 0; + } + + // try reading the results from cache + $result_count = 0; + if ($this->obtain_ids($search_key, $result_count, $id_ary, $start, $per_page, $sort_dir) == SEARCH_RESULT_IN_CACHE) + { + return $result_count; + } + + $id_ary = array(); + + $join_topic = ($type == 'posts') ? false : true; + + // Build sql strings for sorting + $sql_sort = $sort_by_sql[$sort_key] . (($sort_dir == 'a') ? ' ASC' : ' DESC'); + $sql_sort_table = $sql_sort_join = ''; + + switch ($sql_sort[0]) + { + case 'u': + $sql_sort_table = USERS_TABLE . ' u, '; + $sql_sort_join = ($type == 'posts') ? ' AND u.user_id = p.poster_id ' : ' AND u.user_id = t.topic_poster '; + break; + + case 't': + $join_topic = true; + break; + + case 'f': + $sql_sort_table = FORUMS_TABLE . ' f, '; + $sql_sort_join = ' AND f.forum_id = p.forum_id '; + break; + } + + // Build some display specific sql strings + switch ($fields) + { + case 'titleonly': + $sql_match = 'p.post_subject'; + $sql_match_where = ' AND p.post_id = t.topic_first_post_id'; + $join_topic = true; + break; + + case 'msgonly': + $sql_match = 'p.post_text'; + $sql_match_where = ''; + break; + + case 'firstpost': + $sql_match = 'p.post_subject, p.post_text'; + $sql_match_where = ' AND p.post_id = t.topic_first_post_id'; + $join_topic = true; + break; + + default: + $sql_match = 'p.post_subject, p.post_text'; + $sql_match_where = ''; + break; + } + + $sql_select = (!$result_count) ? 'SQL_CALC_FOUND_ROWS ' : ''; + $sql_select = ($type == 'posts') ? $sql_select . 'p.post_id' : 'DISTINCT ' . $sql_select . 't.topic_id'; + $sql_from = ($join_topic) ? TOPICS_TABLE . ' t, ' : ''; + $field = ($type == 'posts') ? 'post_id' : 'topic_id'; + if (sizeof($author_ary) && $author_name) + { + // first one matches post of registered users, second one guests and deleted users + $sql_author = ' AND (' . $this->db->sql_in_set('p.poster_id', array_diff($author_ary, array(ANONYMOUS)), false, true) . ' OR p.post_username ' . $author_name . ')'; + } + else if (sizeof($author_ary)) + { + $sql_author = ' AND ' . $this->db->sql_in_set('p.poster_id', $author_ary); + } + else + { + $sql_author = ''; + } + + $sql_where_options = $sql_sort_join; + $sql_where_options .= ($topic_id) ? ' AND p.topic_id = ' . $topic_id : ''; + $sql_where_options .= ($join_topic) ? ' AND t.topic_id = p.topic_id' : ''; + $sql_where_options .= (sizeof($ex_fid_ary)) ? ' AND ' . $this->db->sql_in_set('p.forum_id', $ex_fid_ary, true) : ''; + $sql_where_options .= ' AND ' . $post_visibility; + $sql_where_options .= $sql_author; + $sql_where_options .= ($sort_days) ? ' AND p.post_time >= ' . (time() - ($sort_days * 86400)) : ''; + $sql_where_options .= $sql_match_where; + + $sql = "SELECT $sql_select + FROM $sql_from$sql_sort_table" . POSTS_TABLE . " p + WHERE MATCH ($sql_match) AGAINST ('" . $this->db->sql_escape(htmlspecialchars_decode($this->search_query)) . "' IN BOOLEAN MODE) + $sql_where_options + ORDER BY $sql_sort"; + $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); + + while ($row = $this->db->sql_fetchrow($result)) + { + $id_ary[] = (int) $row[$field]; + } + $this->db->sql_freeresult($result); + + $id_ary = array_unique($id_ary); + + // if the total result count is not cached yet, retrieve it from the db + if (!$result_count) + { + $sql_found_rows = 'SELECT FOUND_ROWS() as result_count'; + $result = $this->db->sql_query($sql_found_rows); + $result_count = (int) $this->db->sql_fetchfield('result_count'); + $this->db->sql_freeresult($result); + + if (!$result_count) + { + return false; + } + } + + if ($start >= $result_count) + { + $start = floor(($result_count - 1) / $per_page) * $per_page; + + $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); + + while ($row = $this->db->sql_fetchrow($result)) + { + $id_ary[] = (int) $row[$field]; + } + $this->db->sql_freeresult($result); + + $id_ary = array_unique($id_ary); + } + + // store the ids, from start on then delete anything that isn't on the current page because we only need ids for one page + $this->save_ids($search_key, implode(' ', $this->split_words), $author_ary, $result_count, $id_ary, $start, $sort_dir); + $id_ary = array_slice($id_ary, 0, (int) $per_page); + + return $result_count; + } + + /** + * Performs a search on an author's posts without caring about message contents. Depends on display specific params + * + * @param string $type contains either posts or topics depending on what should be searched for + * @param boolean $firstpost_only if true, only topic starting posts will be considered + * @param array $sort_by_sql contains SQL code for the ORDER BY part of a query + * @param string $sort_key is the key of $sort_by_sql for the selected sorting + * @param string $sort_dir is either a or d representing ASC and DESC + * @param string $sort_days specifies the maximum amount of days a post may be old + * @param array $ex_fid_ary specifies an array of forum ids which should not be searched + * @param string $post_visibility specifies which types of posts the user can view in which forums + * @param int $topic_id is set to 0 or a topic id, if it is not 0 then only posts in this topic should be searched + * @param array $author_ary an array of author ids + * @param string $author_name specifies the author match, when ANONYMOUS is also a search-match + * @param array &$id_ary passed by reference, to be filled with ids for the page specified by $start and $per_page, should be ordered + * @param int $start indicates the first index of the page + * @param int $per_page number of ids each page is supposed to contain + * @return boolean|int total number of results + */ + public function author_search($type, $firstpost_only, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $m_approve_fid_ary, $topic_id, $author_ary, $author_name, &$id_ary, &$start, $per_page) + { + // No author? No posts + if (!sizeof($author_ary)) + { + return 0; + } + + // generate a search_key from all the options to identify the results + $search_key = md5(implode('#', array( + '', + $type, + ($firstpost_only) ? 'firstpost' : '', + '', + '', + $sort_days, + $sort_key, + $topic_id, + implode(',', $ex_fid_ary), + $post_visibility, + implode(',', $author_ary), + $author_name, + ))); + + if ($start < 0) + { + $start = 0; + } + + // try reading the results from cache + $result_count = 0; + if ($this->obtain_ids($search_key, $result_count, $id_ary, $start, $per_page, $sort_dir) == SEARCH_RESULT_IN_CACHE) + { + return $result_count; + } + + $id_ary = array(); + + // Create some display specific sql strings + if ($author_name) + { + // first one matches post of registered users, second one guests and deleted users + $sql_author = '(' . $this->db->sql_in_set('p.poster_id', array_diff($author_ary, array(ANONYMOUS)), false, true) . ' OR p.post_username ' . $author_name . ')'; + } + else + { + $sql_author = $this->db->sql_in_set('p.poster_id', $author_ary); + } + $sql_fora = (sizeof($ex_fid_ary)) ? ' AND ' . $this->db->sql_in_set('p.forum_id', $ex_fid_ary, true) : ''; + $sql_topic_id = ($topic_id) ? ' AND p.topic_id = ' . (int) $topic_id : ''; + $sql_time = ($sort_days) ? ' AND p.post_time >= ' . (time() - ($sort_days * 86400)) : ''; + $sql_firstpost = ($firstpost_only) ? ' AND p.post_id = t.topic_first_post_id' : ''; + + // Build sql strings for sorting + $sql_sort = $sort_by_sql[$sort_key] . (($sort_dir == 'a') ? ' ASC' : ' DESC'); + $sql_sort_table = $sql_sort_join = ''; + switch ($sql_sort[0]) + { + case 'u': + $sql_sort_table = USERS_TABLE . ' u, '; + $sql_sort_join = ($type == 'posts') ? ' AND u.user_id = p.poster_id ' : ' AND u.user_id = t.topic_poster '; + break; + + case 't': + $sql_sort_table = ($type == 'posts' && !$firstpost_only) ? TOPICS_TABLE . ' t, ' : ''; + $sql_sort_join = ($type == 'posts' && !$firstpost_only) ? ' AND t.topic_id = p.topic_id ' : ''; + break; + + case 'f': + $sql_sort_table = FORUMS_TABLE . ' f, '; + $sql_sort_join = ' AND f.forum_id = p.forum_id '; + break; + } + + $m_approve_fid_sql = ' AND ' . $post_visibility; + + // If the cache was completely empty count the results + $calc_results = ($result_count) ? '' : 'SQL_CALC_FOUND_ROWS '; + + // Build the query for really selecting the post_ids + if ($type == 'posts') + { + $sql = "SELECT {$calc_results}p.post_id + FROM " . $sql_sort_table . POSTS_TABLE . ' p' . (($firstpost_only) ? ', ' . TOPICS_TABLE . ' t ' : ' ') . " + WHERE $sql_author + $sql_topic_id + $sql_firstpost + $m_approve_fid_sql + $sql_fora + $sql_sort_join + $sql_time + ORDER BY $sql_sort"; + $field = 'post_id'; + } + else + { + $sql = "SELECT {$calc_results}t.topic_id + FROM " . $sql_sort_table . TOPICS_TABLE . ' t, ' . POSTS_TABLE . " p + WHERE $sql_author + $sql_topic_id + $sql_firstpost + $m_approve_fid_sql + $sql_fora + AND t.topic_id = p.topic_id + $sql_sort_join + $sql_time + GROUP BY t.topic_id + ORDER BY $sql_sort"; + $field = 'topic_id'; + } + + // Only read one block of posts from the db and then cache it + $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); + + while ($row = $this->db->sql_fetchrow($result)) + { + $id_ary[] = (int) $row[$field]; + } + $this->db->sql_freeresult($result); + + // retrieve the total result count if needed + if (!$result_count) + { + $sql_found_rows = 'SELECT FOUND_ROWS() as result_count'; + $result = $this->db->sql_query($sql_found_rows); + $result_count = (int) $this->db->sql_fetchfield('result_count'); + $this->db->sql_freeresult($result); + + if (!$result_count) + { + return false; + } + } + + if ($start >= $result_count) + { + $start = floor(($result_count - 1) / $per_page) * $per_page; + + $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); + while ($row = $this->db->sql_fetchrow($result)) + { + $id_ary[] = (int) $row[$field]; + } + $this->db->sql_freeresult($result); + + $id_ary = array_unique($id_ary); + } + + if (sizeof($id_ary)) + { + $this->save_ids($search_key, '', $author_ary, $result_count, $id_ary, $start, $sort_dir); + $id_ary = array_slice($id_ary, 0, $per_page); + + return $result_count; + } + return false; + } + + /** + * Destroys cached search results, that contained one of the new words in a post so the results won't be outdated + * + * @param string $mode contains the post mode: edit, post, reply, quote ... + * @param int $post_id contains the post id of the post to index + * @param string $message contains the post text of the post + * @param string $subject contains the subject of the post to index + * @param int $poster_id contains the user id of the poster + * @param int $forum_id contains the forum id of parent forum of the post + */ + public function index($mode, $post_id, &$message, &$subject, $poster_id, $forum_id) + { + // Split old and new post/subject to obtain array of words + $split_text = $this->split_message($message); + $split_title = ($subject) ? $this->split_message($subject) : array(); + + $words = array_unique(array_merge($split_text, $split_title)); + + unset($split_text); + unset($split_title); + + // destroy cached search results containing any of the words removed or added + $this->destroy_cache($words, array($poster_id)); + + unset($words); + } + + /** + * Destroy cached results, that might be outdated after deleting a post + */ + public function index_remove($post_ids, $author_ids, $forum_ids) + { + $this->destroy_cache(array(), array_unique($author_ids)); + } + + /** + * Destroy old cache entries + */ + public function tidy() + { + // destroy too old cached search results + $this->destroy_cache(array()); + + set_config('search_last_gc', time(), true); + } + + /** + * Create fulltext index + * + * @return string|bool error string is returned incase of errors otherwise false + */ + public function create_index($acp_module, $u_action) + { + // Make sure we can actually use MySQL with fulltext indexes + if ($error = $this->init()) + { + return $error; + } + + if (empty($this->stats)) + { + $this->get_stats(); + } + + $alter = array(); + + if (!isset($this->stats['post_subject'])) + { + if ($this->db->sql_layer == 'mysqli' || version_compare($this->db->sql_server_info(true), '4.1.3', '>=')) + { + $alter[] = 'MODIFY post_subject varchar(255) COLLATE utf8_unicode_ci DEFAULT \'\' NOT NULL'; + } + else + { + $alter[] = 'MODIFY post_subject text NOT NULL'; + } + $alter[] = 'ADD FULLTEXT (post_subject)'; + } + + if (!isset($this->stats['post_text'])) + { + if ($this->db->sql_layer == 'mysqli' || version_compare($this->db->sql_server_info(true), '4.1.3', '>=')) + { + $alter[] = 'MODIFY post_text mediumtext COLLATE utf8_unicode_ci NOT NULL'; + } + else + { + $alter[] = 'MODIFY post_text mediumtext NOT NULL'; + } + $alter[] = 'ADD FULLTEXT (post_text)'; + } + + if (!isset($this->stats['post_content'])) + { + $alter[] = 'ADD FULLTEXT post_content (post_subject, post_text)'; + } + + if (sizeof($alter)) + { + $this->db->sql_query('ALTER TABLE ' . POSTS_TABLE . ' ' . implode(', ', $alter)); + } + + $this->db->sql_query('TRUNCATE TABLE ' . SEARCH_RESULTS_TABLE); + + return false; + } + + /** + * Drop fulltext index + * + * @return string|bool error string is returned incase of errors otherwise false + */ + public function delete_index($acp_module, $u_action) + { + // Make sure we can actually use MySQL with fulltext indexes + if ($error = $this->init()) + { + return $error; + } + + if (empty($this->stats)) + { + $this->get_stats(); + } + + $alter = array(); + + if (isset($this->stats['post_subject'])) + { + $alter[] = 'DROP INDEX post_subject'; + } + + if (isset($this->stats['post_text'])) + { + $alter[] = 'DROP INDEX post_text'; + } + + if (isset($this->stats['post_content'])) + { + $alter[] = 'DROP INDEX post_content'; + } + + if (sizeof($alter)) + { + $this->db->sql_query('ALTER TABLE ' . POSTS_TABLE . ' ' . implode(', ', $alter)); + } + + $this->db->sql_query('TRUNCATE TABLE ' . SEARCH_RESULTS_TABLE); + + return false; + } + + /** + * Returns true if both FULLTEXT indexes exist + */ + public function index_created() + { + if (empty($this->stats)) + { + $this->get_stats(); + } + + return (isset($this->stats['post_text']) && isset($this->stats['post_subject']) && isset($this->stats['post_content'])) ? true : false; + } + + /** + * Returns an associative array containing information about the indexes + */ + public function index_stats() + { + if (empty($this->stats)) + { + $this->get_stats(); + } + + return array( + $this->user->lang['FULLTEXT_MYSQL_TOTAL_POSTS'] => ($this->index_created()) ? $this->stats['total_posts'] : 0, + ); + } + + /** + * Computes the stats and store them in the $this->stats associative array + */ + protected function get_stats() + { + if (strpos($this->db->sql_layer, 'mysql') === false) + { + $this->stats = array(); + return; + } + + $sql = 'SHOW INDEX + FROM ' . POSTS_TABLE; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + // deal with older MySQL versions which didn't use Index_type + $index_type = (isset($row['Index_type'])) ? $row['Index_type'] : $row['Comment']; + + if ($index_type == 'FULLTEXT') + { + if ($row['Key_name'] == 'post_text') + { + $this->stats['post_text'] = $row; + } + else if ($row['Key_name'] == 'post_subject') + { + $this->stats['post_subject'] = $row; + } + else if ($row['Key_name'] == 'post_content') + { + $this->stats['post_content'] = $row; + } + } + } + $this->db->sql_freeresult($result); + + $this->stats['total_posts'] = empty($this->stats) ? 0 : $this->db->get_estimated_row_count(POSTS_TABLE); + } + + /** + * Display a note, that UTF-8 support is not available with certain versions of PHP + * + * @return associative array containing template and config variables + */ + public function acp() + { + $tpl = ' +
+

' . $this->user->lang['FULLTEXT_MYSQL_MIN_SEARCH_CHARS_EXPLAIN'] . '
+
' . $this->config['fulltext_mysql_min_word_len'] . '
+
+
+

' . $this->user->lang['FULLTEXT_MYSQL_MAX_SEARCH_CHARS_EXPLAIN'] . '
+
' . $this->config['fulltext_mysql_max_word_len'] . '
+
+ '; + + // These are fields required in the config table + return array( + 'tpl' => $tpl, + 'config' => array() + ); + } +} diff --git a/phpBB/phpbb/search/fulltext_native.php b/phpBB/phpbb/search/fulltext_native.php new file mode 100644 index 0000000000..730c3a6c2d --- /dev/null +++ b/phpBB/phpbb/search/fulltext_native.php @@ -0,0 +1,1821 @@ +phpbb_root_path = $phpbb_root_path; + $this->php_ext = $phpEx; + $this->config = $config; + $this->db = $db; + $this->user = $user; + + $this->word_length = array('min' => $this->config['fulltext_native_min_chars'], 'max' => $this->config['fulltext_native_max_chars']); + + /** + * Load the UTF tools + */ + if (!class_exists('utf_normalizer')) + { + include($this->phpbb_root_path . 'includes/utf/utf_normalizer.' . $this->php_ext); + } + if (!function_exists('utf8_decode_ncr')) + { + include($this->phpbb_root_path . 'includes/utf/utf_tools.' . $this->php_ext); + } + + $error = false; + } + + /** + * Returns the name of this search backend to be displayed to administrators + * + * @return string Name + */ + public function get_name() + { + return 'phpBB Native Fulltext'; + } + + /** + * Returns the search_query + * + * @return string search query + */ + public function get_search_query() + { + return $this->search_query; + } + + /** + * Returns the common_words array + * + * @return array common words that are ignored by search backend + */ + public function get_common_words() + { + return $this->common_words; + } + + /** + * Returns the word_length array + * + * @return array min and max word length for searching + */ + public function get_word_length() + { + return $this->word_length; + } + + /** + * This function fills $this->search_query with the cleaned user search query + * + * If $terms is 'any' then the words will be extracted from the search query + * and combined with | inside brackets. They will afterwards be treated like + * an standard search query. + * + * Then it analyses the query and fills the internal arrays $must_not_contain_ids, + * $must_contain_ids and $must_exclude_one_ids which are later used by keyword_search() + * + * @param string $keywords contains the search query string as entered by the user + * @param string $terms is either 'all' (use search query as entered, default words to 'must be contained in post') + * or 'any' (find all posts containing at least one of the given words) + * @return boolean false if no valid keywords were found and otherwise true + */ + public function split_keywords($keywords, $terms) + { + $tokens = '+-|()*'; + + $keywords = trim($this->cleanup($keywords, $tokens)); + + // allow word|word|word without brackets + if ((strpos($keywords, ' ') === false) && (strpos($keywords, '|') !== false) && (strpos($keywords, '(') === false)) + { + $keywords = '(' . $keywords . ')'; + } + + $open_bracket = $space = false; + for ($i = 0, $n = strlen($keywords); $i < $n; $i++) + { + if ($open_bracket !== false) + { + switch ($keywords[$i]) + { + case ')': + if ($open_bracket + 1 == $i) + { + $keywords[$i - 1] = '|'; + $keywords[$i] = '|'; + } + $open_bracket = false; + break; + case '(': + $keywords[$i] = '|'; + break; + case '+': + case '-': + case ' ': + $keywords[$i] = '|'; + break; + case '*': + if ($i === 0 || ($keywords[$i - 1] !== '*' && strcspn($keywords[$i - 1], $tokens) === 0)) + { + if ($i === $n - 1 || ($keywords[$i + 1] !== '*' && strcspn($keywords[$i + 1], $tokens) === 0)) + { + $keywords = substr($keywords, 0, $i) . substr($keywords, $i + 1); + } + } + break; + } + } + else + { + switch ($keywords[$i]) + { + case ')': + $keywords[$i] = ' '; + break; + case '(': + $open_bracket = $i; + $space = false; + break; + case '|': + $keywords[$i] = ' '; + break; + case '-': + case '+': + $space = $keywords[$i]; + break; + case ' ': + if ($space !== false) + { + $keywords[$i] = $space; + } + break; + default: + $space = false; + } + } + } + + if ($open_bracket) + { + $keywords .= ')'; + } + + $match = array( + '# +#', + '#\|\|+#', + '#(\+|\-)(?:\+|\-)+#', + '#\(\|#', + '#\|\)#', + ); + $replace = array( + ' ', + '|', + '$1', + '(', + ')', + ); + + $keywords = preg_replace($match, $replace, $keywords); + $num_keywords = sizeof(explode(' ', $keywords)); + + // We limit the number of allowed keywords to minimize load on the database + if ($this->config['max_num_search_keywords'] && $num_keywords > $this->config['max_num_search_keywords']) + { + trigger_error($this->user->lang('MAX_NUM_SEARCH_KEYWORDS_REFINE', $this->config['max_num_search_keywords'], $num_keywords)); + } + + // $keywords input format: each word separated by a space, words in a bracket are not separated + + // the user wants to search for any word, convert the search query + if ($terms == 'any') + { + $words = array(); + + preg_match_all('#([^\\s+\\-|()]+)(?:$|[\\s+\\-|()])#u', $keywords, $words); + if (sizeof($words[1])) + { + $keywords = '(' . implode('|', $words[1]) . ')'; + } + } + + // set the search_query which is shown to the user + $this->search_query = $keywords; + + $exact_words = array(); + preg_match_all('#([^\\s+\\-|*()]+)(?:$|[\\s+\\-|()])#u', $keywords, $exact_words); + $exact_words = $exact_words[1]; + + $common_ids = $words = array(); + + if (sizeof($exact_words)) + { + $sql = 'SELECT word_id, word_text, word_common + FROM ' . SEARCH_WORDLIST_TABLE . ' + WHERE ' . $this->db->sql_in_set('word_text', $exact_words) . ' + ORDER BY word_count ASC'; + $result = $this->db->sql_query($sql); + + // store an array of words and ids, remove common words + while ($row = $this->db->sql_fetchrow($result)) + { + if ($row['word_common']) + { + $this->common_words[] = $row['word_text']; + $common_ids[$row['word_text']] = (int) $row['word_id']; + continue; + } + + $words[$row['word_text']] = (int) $row['word_id']; + } + $this->db->sql_freeresult($result); + } + unset($exact_words); + + // now analyse the search query, first split it using the spaces + $query = explode(' ', $keywords); + + $this->must_contain_ids = array(); + $this->must_not_contain_ids = array(); + $this->must_exclude_one_ids = array(); + + $mode = ''; + $ignore_no_id = true; + + foreach ($query as $word) + { + if (empty($word)) + { + continue; + } + + // words which should not be included + if ($word[0] == '-') + { + $word = substr($word, 1); + + // a group of which at least one may not be in the resulting posts + if ($word[0] == '(') + { + $word = array_unique(explode('|', substr($word, 1, -1))); + $mode = 'must_exclude_one'; + } + // one word which should not be in the resulting posts + else + { + $mode = 'must_not_contain'; + } + $ignore_no_id = true; + } + // words which have to be included + else + { + // no prefix is the same as a +prefix + if ($word[0] == '+') + { + $word = substr($word, 1); + } + + // a group of words of which at least one word should be in every resulting post + if ($word[0] == '(') + { + $word = array_unique(explode('|', substr($word, 1, -1))); + } + $ignore_no_id = false; + $mode = 'must_contain'; + } + + if (empty($word)) + { + continue; + } + + // if this is an array of words then retrieve an id for each + if (is_array($word)) + { + $non_common_words = array(); + $id_words = array(); + foreach ($word as $i => $word_part) + { + if (strpos($word_part, '*') !== false) + { + $id_words[] = '\'' . $this->db->sql_escape(str_replace('*', '%', $word_part)) . '\''; + $non_common_words[] = $word_part; + } + else if (isset($words[$word_part])) + { + $id_words[] = $words[$word_part]; + $non_common_words[] = $word_part; + } + else + { + $len = utf8_strlen($word_part); + if ($len < $this->word_length['min'] || $len > $this->word_length['max']) + { + $this->common_words[] = $word_part; + } + } + } + if (sizeof($id_words)) + { + sort($id_words); + if (sizeof($id_words) > 1) + { + $this->{$mode . '_ids'}[] = $id_words; + } + else + { + $mode = ($mode == 'must_exclude_one') ? 'must_not_contain' : $mode; + $this->{$mode . '_ids'}[] = $id_words[0]; + } + } + // throw an error if we shall not ignore unexistant words + else if (!$ignore_no_id && sizeof($non_common_words)) + { + trigger_error(sprintf($user->lang['WORDS_IN_NO_POST'], implode($user->lang['COMMA_SEPARATOR'], $non_common_words))); + } + unset($non_common_words); + } + // else we only need one id + else if (($wildcard = strpos($word, '*') !== false) || isset($words[$word])) + { + if ($wildcard) + { + $len = utf8_strlen(str_replace('*', '', $word)); + if ($len >= $this->word_length['min'] && $len <= $this->word_length['max']) + { + $this->{$mode . '_ids'}[] = '\'' . $this->db->sql_escape(str_replace('*', '%', $word)) . '\''; + } + else + { + $this->common_words[] = $word; + } + } + else + { + $this->{$mode . '_ids'}[] = $words[$word]; + } + } + // throw an error if we shall not ignore unexistant words + else if (!$ignore_no_id) + { + if (!isset($common_ids[$word])) + { + $len = utf8_strlen($word); + if ($len >= $this->word_length['min'] && $len <= $this->word_length['max']) + { + trigger_error(sprintf($this->user->lang['WORD_IN_NO_POST'], $word)); + } + else + { + $this->common_words[] = $word; + } + } + } + else + { + $len = utf8_strlen($word); + if ($len < $this->word_length['min'] || $len > $this->word_length['max']) + { + $this->common_words[] = $word; + } + } + } + + // we can't search for negatives only + if (!sizeof($this->must_contain_ids)) + { + return false; + } + + if (!empty($this->search_query)) + { + return true; + } + return false; + } + + /** + * Performs a search on keywords depending on display specific params. You have to run split_keywords() first + * + * @param string $type contains either posts or topics depending on what should be searched for + * @param string $fields contains either titleonly (topic titles should be searched), msgonly (only message bodies should be searched), firstpost (only subject and body of the first post should be searched) or all (all post bodies and subjects should be searched) + * @param string $terms is either 'all' (use query as entered, words without prefix should default to "have to be in field") or 'any' (ignore search query parts and just return all posts that contain any of the specified words) + * @param array $sort_by_sql contains SQL code for the ORDER BY part of a query + * @param string $sort_key is the key of $sort_by_sql for the selected sorting + * @param string $sort_dir is either a or d representing ASC and DESC + * @param string $sort_days specifies the maximum amount of days a post may be old + * @param array $ex_fid_ary specifies an array of forum ids which should not be searched + * @param string $post_visibility specifies which types of posts the user can view in which forums + * @param int $topic_id is set to 0 or a topic id, if it is not 0 then only posts in this topic should be searched + * @param array $author_ary an array of author ids if the author should be ignored during the search the array is empty + * @param string $author_name specifies the author match, when ANONYMOUS is also a search-match + * @param array &$id_ary passed by reference, to be filled with ids for the page specified by $start and $per_page, should be ordered + * @param int $start indicates the first index of the page + * @param int $per_page number of ids each page is supposed to contain + * @return boolean|int total number of results + */ + public function keyword_search($type, $fields, $terms, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $post_visibility, $topic_id, $author_ary, $author_name, &$id_ary, &$start, $per_page) + { + // No keywords? No posts. + if (empty($this->search_query)) + { + return false; + } + + $must_contain_ids = $this->must_contain_ids; + $must_not_contain_ids = $this->must_not_contain_ids; + $must_exclude_one_ids = $this->must_exclude_one_ids; + + sort($must_contain_ids); + sort($must_not_contain_ids); + sort($must_exclude_one_ids); + + // generate a search_key from all the options to identify the results + $search_key = md5(implode('#', array( + serialize($must_contain_ids), + serialize($must_not_contain_ids), + serialize($must_exclude_one_ids), + $type, + $fields, + $terms, + $sort_days, + $sort_key, + $topic_id, + implode(',', $ex_fid_ary), + $post_visibility, + implode(',', $author_ary), + $author_name, + ))); + + // try reading the results from cache + $total_results = 0; + if ($this->obtain_ids($search_key, $total_results, $id_ary, $start, $per_page, $sort_dir) == SEARCH_RESULT_IN_CACHE) + { + return $total_results; + } + + $id_ary = array(); + + $sql_where = array(); + $group_by = false; + $m_num = 0; + $w_num = 0; + + $sql_array = array( + 'SELECT' => ($type == 'posts') ? 'p.post_id' : 'p.topic_id', + 'FROM' => array( + SEARCH_WORDMATCH_TABLE => array(), + SEARCH_WORDLIST_TABLE => array(), + ), + 'LEFT_JOIN' => array(array( + 'FROM' => array(POSTS_TABLE => 'p'), + 'ON' => 'm0.post_id = p.post_id', + )), + ); + + $title_match = ''; + $left_join_topics = false; + $group_by = true; + // Build some display specific sql strings + switch ($fields) + { + case 'titleonly': + $title_match = 'title_match = 1'; + $group_by = false; + // no break + case 'firstpost': + $left_join_topics = true; + $sql_where[] = 'p.post_id = t.topic_first_post_id'; + break; + + case 'msgonly': + $title_match = 'title_match = 0'; + $group_by = false; + break; + } + + if ($type == 'topics') + { + $left_join_topics = true; + $group_by = true; + } + + /** + * @todo Add a query optimizer (handle stuff like "+(4|3) +4") + */ + + foreach ($this->must_contain_ids as $subquery) + { + if (is_array($subquery)) + { + $group_by = true; + + $word_id_sql = array(); + $word_ids = array(); + foreach ($subquery as $id) + { + if (is_string($id)) + { + $sql_array['LEFT_JOIN'][] = array( + 'FROM' => array(SEARCH_WORDLIST_TABLE => 'w' . $w_num), + 'ON' => "w$w_num.word_text LIKE $id" + ); + $word_ids[] = "w$w_num.word_id"; + + $w_num++; + } + else + { + $word_ids[] = $id; + } + } + + $sql_where[] = $this->db->sql_in_set("m$m_num.word_id", $word_ids); + + unset($word_id_sql); + unset($word_ids); + } + else if (is_string($subquery)) + { + $sql_array['FROM'][SEARCH_WORDLIST_TABLE][] = 'w' . $w_num; + + $sql_where[] = "w$w_num.word_text LIKE $subquery"; + $sql_where[] = "m$m_num.word_id = w$w_num.word_id"; + + $group_by = true; + $w_num++; + } + else + { + $sql_where[] = "m$m_num.word_id = $subquery"; + } + + $sql_array['FROM'][SEARCH_WORDMATCH_TABLE][] = 'm' . $m_num; + + if ($title_match) + { + $sql_where[] = "m$m_num.$title_match"; + } + + if ($m_num != 0) + { + $sql_where[] = "m$m_num.post_id = m0.post_id"; + } + $m_num++; + } + + foreach ($this->must_not_contain_ids as $key => $subquery) + { + if (is_string($subquery)) + { + $sql_array['LEFT_JOIN'][] = array( + 'FROM' => array(SEARCH_WORDLIST_TABLE => 'w' . $w_num), + 'ON' => "w$w_num.word_text LIKE $subquery" + ); + + $this->must_not_contain_ids[$key] = "w$w_num.word_id"; + + $group_by = true; + $w_num++; + } + } + + if (sizeof($this->must_not_contain_ids)) + { + $sql_array['LEFT_JOIN'][] = array( + 'FROM' => array(SEARCH_WORDMATCH_TABLE => 'm' . $m_num), + 'ON' => $this->db->sql_in_set("m$m_num.word_id", $this->must_not_contain_ids) . (($title_match) ? " AND m$m_num.$title_match" : '') . " AND m$m_num.post_id = m0.post_id" + ); + + $sql_where[] = "m$m_num.word_id IS NULL"; + $m_num++; + } + + foreach ($this->must_exclude_one_ids as $ids) + { + $is_null_joins = array(); + foreach ($ids as $id) + { + if (is_string($id)) + { + $sql_array['LEFT_JOIN'][] = array( + 'FROM' => array(SEARCH_WORDLIST_TABLE => 'w' . $w_num), + 'ON' => "w$w_num.word_text LIKE $id" + ); + $id = "w$w_num.word_id"; + + $group_by = true; + $w_num++; + } + + $sql_array['LEFT_JOIN'][] = array( + 'FROM' => array(SEARCH_WORDMATCH_TABLE => 'm' . $m_num), + 'ON' => "m$m_num.word_id = $id AND m$m_num.post_id = m0.post_id" . (($title_match) ? " AND m$m_num.$title_match" : '') + ); + $is_null_joins[] = "m$m_num.word_id IS NULL"; + + $m_num++; + } + $sql_where[] = '(' . implode(' OR ', $is_null_joins) . ')'; + } + + $sql_where[] = $post_visibility; + + if ($topic_id) + { + $sql_where[] = 'p.topic_id = ' . $topic_id; + } + + if (sizeof($author_ary)) + { + if ($author_name) + { + // first one matches post of registered users, second one guests and deleted users + $sql_author = '(' . $this->db->sql_in_set('p.poster_id', array_diff($author_ary, array(ANONYMOUS)), false, true) . ' OR p.post_username ' . $author_name . ')'; + } + else + { + $sql_author = $this->db->sql_in_set('p.poster_id', $author_ary); + } + $sql_where[] = $sql_author; + } + + if (sizeof($ex_fid_ary)) + { + $sql_where[] = $this->db->sql_in_set('p.forum_id', $ex_fid_ary, true); + } + + if ($sort_days) + { + $sql_where[] = 'p.post_time >= ' . (time() - ($sort_days * 86400)); + } + + $sql_array['WHERE'] = implode(' AND ', $sql_where); + + $is_mysql = false; + // if the total result count is not cached yet, retrieve it from the db + if (!$total_results) + { + $sql = ''; + $sql_array_count = $sql_array; + + if ($left_join_topics) + { + $sql_array_count['LEFT_JOIN'][] = array( + 'FROM' => array(TOPICS_TABLE => 't'), + 'ON' => 'p.topic_id = t.topic_id' + ); + } + + switch ($this->db->sql_layer) + { + case 'mysql4': + case 'mysqli': + + // 3.x does not support SQL_CALC_FOUND_ROWS + // $sql_array['SELECT'] = 'SQL_CALC_FOUND_ROWS ' . $sql_array['SELECT']; + $is_mysql = true; + + break; + + case 'sqlite': + $sql_array_count['SELECT'] = ($type == 'posts') ? 'DISTINCT p.post_id' : 'DISTINCT p.topic_id'; + $sql = 'SELECT COUNT(' . (($type == 'posts') ? 'post_id' : 'topic_id') . ') as total_results + FROM (' . $this->db->sql_build_query('SELECT', $sql_array_count) . ')'; + + // no break + + default: + $sql_array_count['SELECT'] = ($type == 'posts') ? 'COUNT(DISTINCT p.post_id) AS total_results' : 'COUNT(DISTINCT p.topic_id) AS total_results'; + $sql = (!$sql) ? $this->db->sql_build_query('SELECT', $sql_array_count) : $sql; + + $result = $this->db->sql_query($sql); + $total_results = (int) $this->db->sql_fetchfield('total_results'); + $this->db->sql_freeresult($result); + + if (!$total_results) + { + return false; + } + break; + } + + unset($sql_array_count, $sql); + } + + // Build sql strings for sorting + $sql_sort = $sort_by_sql[$sort_key] . (($sort_dir == 'a') ? ' ASC' : ' DESC'); + + switch ($sql_sort[0]) + { + case 'u': + $sql_array['FROM'][USERS_TABLE] = 'u'; + $sql_where[] = 'u.user_id = p.poster_id '; + break; + + case 't': + $left_join_topics = true; + break; + + case 'f': + $sql_array['FROM'][FORUMS_TABLE] = 'f'; + $sql_where[] = 'f.forum_id = p.forum_id'; + break; + } + + if ($left_join_topics) + { + $sql_array['LEFT_JOIN'][] = array( + 'FROM' => array(TOPICS_TABLE => 't'), + 'ON' => 'p.topic_id = t.topic_id' + ); + } + + $sql_array['WHERE'] = implode(' AND ', $sql_where); + $sql_array['GROUP_BY'] = ($group_by) ? (($type == 'posts') ? 'p.post_id' : 'p.topic_id') . ', ' . $sort_by_sql[$sort_key] : ''; + $sql_array['ORDER_BY'] = $sql_sort; + + unset($sql_where, $sql_sort, $group_by); + + $sql = $this->db->sql_build_query('SELECT', $sql_array); + $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); + + while ($row = $this->db->sql_fetchrow($result)) + { + $id_ary[] = (int) $row[(($type == 'posts') ? 'post_id' : 'topic_id')]; + } + $this->db->sql_freeresult($result); + + + // if we use mysql and the total result count is not cached yet, retrieve it from the db + if (!$total_results && $is_mysql) + { + // Count rows for the executed queries. Replace $select within $sql with SQL_CALC_FOUND_ROWS, and run it + $sql_array_copy = $sql_array; + $sql_array_copy['SELECT'] = 'SQL_CALC_FOUND_ROWS p.post_id '; + + $sql_calc = $this->db->sql_build_query('SELECT', $sql_array_copy); + unset($sql_array_copy); + + $this->db->sql_query($sql_calc); + $this->db->sql_freeresult($result); + + $sql_count = 'SELECT FOUND_ROWS() as total_results'; + $result = $this->db->sql_query($sql_count); + $total_results = (int) $this->db->sql_fetchfield('total_results'); + $this->db->sql_freeresult($result); + + if (!$total_results) + { + return false; + } + } + + if ($start >= $total_results) + { + $start = floor(($total_results - 1) / $per_page) * $per_page; + + $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); + + while ($row = $this->db->sql_fetchrow($result)) + { + $id_ary[] = (int) $row[(($type == 'posts') ? 'post_id' : 'topic_id')]; + } + $this->db->sql_freeresult($result); + + } + + // store the ids, from start on then delete anything that isn't on the current page because we only need ids for one page + $this->save_ids($search_key, $this->search_query, $author_ary, $total_results, $id_ary, $start, $sort_dir); + $id_ary = array_slice($id_ary, 0, (int) $per_page); + + return $total_results; + } + + /** + * Performs a search on an author's posts without caring about message contents. Depends on display specific params + * + * @param string $type contains either posts or topics depending on what should be searched for + * @param boolean $firstpost_only if true, only topic starting posts will be considered + * @param array $sort_by_sql contains SQL code for the ORDER BY part of a query + * @param string $sort_key is the key of $sort_by_sql for the selected sorting + * @param string $sort_dir is either a or d representing ASC and DESC + * @param string $sort_days specifies the maximum amount of days a post may be old + * @param array $ex_fid_ary specifies an array of forum ids which should not be searched + * @param string $post_visibility specifies which types of posts the user can view in which forums + * @param int $topic_id is set to 0 or a topic id, if it is not 0 then only posts in this topic should be searched + * @param array $author_ary an array of author ids + * @param string $author_name specifies the author match, when ANONYMOUS is also a search-match + * @param array &$id_ary passed by reference, to be filled with ids for the page specified by $start and $per_page, should be ordered + * @param int $start indicates the first index of the page + * @param int $per_page number of ids each page is supposed to contain + * @return boolean|int total number of results + */ + public function author_search($type, $firstpost_only, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $post_visibility, $topic_id, $author_ary, $author_name, &$id_ary, &$start, $per_page) + { + // No author? No posts + if (!sizeof($author_ary)) + { + return 0; + } + + // generate a search_key from all the options to identify the results + $search_key = md5(implode('#', array( + '', + $type, + ($firstpost_only) ? 'firstpost' : '', + '', + '', + $sort_days, + $sort_key, + $topic_id, + implode(',', $ex_fid_ary), + $post_visibility, + implode(',', $author_ary), + $author_name, + ))); + + // try reading the results from cache + $total_results = 0; + if ($this->obtain_ids($search_key, $total_results, $id_ary, $start, $per_page, $sort_dir) == SEARCH_RESULT_IN_CACHE) + { + return $total_results; + } + + $id_ary = array(); + + // Create some display specific sql strings + if ($author_name) + { + // first one matches post of registered users, second one guests and deleted users + $sql_author = '(' . $this->db->sql_in_set('p.poster_id', array_diff($author_ary, array(ANONYMOUS)), false, true) . ' OR p.post_username ' . $author_name . ')'; + } + else + { + $sql_author = $this->db->sql_in_set('p.poster_id', $author_ary); + } + $sql_fora = (sizeof($ex_fid_ary)) ? ' AND ' . $this->db->sql_in_set('p.forum_id', $ex_fid_ary, true) : ''; + $sql_time = ($sort_days) ? ' AND p.post_time >= ' . (time() - ($sort_days * 86400)) : ''; + $sql_topic_id = ($topic_id) ? ' AND p.topic_id = ' . (int) $topic_id : ''; + $sql_firstpost = ($firstpost_only) ? ' AND p.post_id = t.topic_first_post_id' : ''; + $post_visibility = ($post_visibility) ? ' AND ' . $post_visibility : ''; + + // Build sql strings for sorting + $sql_sort = $sort_by_sql[$sort_key] . (($sort_dir == 'a') ? ' ASC' : ' DESC'); + $sql_sort_table = $sql_sort_join = ''; + switch ($sql_sort[0]) + { + case 'u': + $sql_sort_table = USERS_TABLE . ' u, '; + $sql_sort_join = ' AND u.user_id = p.poster_id '; + break; + + case 't': + $sql_sort_table = ($type == 'posts' && !$firstpost_only) ? TOPICS_TABLE . ' t, ' : ''; + $sql_sort_join = ($type == 'posts' && !$firstpost_only) ? ' AND t.topic_id = p.topic_id ' : ''; + break; + + case 'f': + $sql_sort_table = FORUMS_TABLE . ' f, '; + $sql_sort_join = ' AND f.forum_id = p.forum_id '; + break; + } + + $select = ($type == 'posts') ? 'p.post_id' : 't.topic_id'; + $is_mysql = false; + + // If the cache was completely empty count the results + if (!$total_results) + { + switch ($this->db->sql_layer) + { + case 'mysql4': + case 'mysqli': +// $select = 'SQL_CALC_FOUND_ROWS ' . $select; + $is_mysql = true; + break; + + default: + if ($type == 'posts') + { + $sql = 'SELECT COUNT(p.post_id) as total_results + FROM ' . POSTS_TABLE . ' p' . (($firstpost_only) ? ', ' . TOPICS_TABLE . ' t ' : ' ') . " + WHERE $sql_author + $sql_topic_id + $sql_firstpost + $post_visibility + $sql_fora + $sql_time"; + } + else + { + if ($this->db->sql_layer == 'sqlite') + { + $sql = 'SELECT COUNT(topic_id) as total_results + FROM (SELECT DISTINCT t.topic_id'; + } + else + { + $sql = 'SELECT COUNT(DISTINCT t.topic_id) as total_results'; + } + + $sql .= ' FROM ' . TOPICS_TABLE . ' t, ' . POSTS_TABLE . " p + WHERE $sql_author + $sql_topic_id + $sql_firstpost + $post_visibility + $sql_fora + AND t.topic_id = p.topic_id + $sql_time" . (($this->db->sql_layer == 'sqlite') ? ')' : ''); + } + $result = $this->db->sql_query($sql); + + $total_results = (int) $this->db->sql_fetchfield('total_results'); + $this->db->sql_freeresult($result); + + if (!$total_results) + { + return false; + } + break; + } + } + + // Build the query for really selecting the post_ids + if ($type == 'posts') + { + $sql = "SELECT $select + FROM " . $sql_sort_table . POSTS_TABLE . ' p' . (($firstpost_only) ? ', ' . TOPICS_TABLE . ' t' : '') . " + WHERE $sql_author + $sql_topic_id + $sql_firstpost + $post_visibility + $sql_fora + $sql_sort_join + $sql_time + ORDER BY $sql_sort"; + $field = 'post_id'; + } + else + { + $sql = "SELECT $select + FROM " . $sql_sort_table . TOPICS_TABLE . ' t, ' . POSTS_TABLE . " p + WHERE $sql_author + $sql_topic_id + $sql_firstpost + $post_visibility + $sql_fora + AND t.topic_id = p.topic_id + $sql_sort_join + $sql_time + GROUP BY t.topic_id, " . $sort_by_sql[$sort_key] . ' + ORDER BY ' . $sql_sort; + $field = 'topic_id'; + } + + // Only read one block of posts from the db and then cache it + $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); + + while ($row = $this->db->sql_fetchrow($result)) + { + $id_ary[] = (int) $row[$field]; + } + $this->db->sql_freeresult($result); + + if (!$total_results && $is_mysql) + { + // Count rows for the executed queries. Replace $select within $sql with SQL_CALC_FOUND_ROWS, and run it. + $sql_calc = str_replace('SELECT ' . $select, 'SELECT DISTINCT SQL_CALC_FOUND_ROWS p.post_id', $sql); + + $this->db->sql_query($sql_calc); + $this->db->sql_freeresult($result); + + $sql_count = 'SELECT FOUND_ROWS() as total_results'; + $result = $this->db->sql_query($sql_count); + $total_results = (int) $this->db->sql_fetchfield('total_results'); + $this->db->sql_freeresult($result); + + if (!$total_results) + { + return false; + } + } + + if ($start >= $total_results) + { + $start = floor(($total_results - 1) / $per_page) * $per_page; + + $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); + + while ($row = $this->db->sql_fetchrow($result)) + { + $id_ary[] = (int) $row[$field]; + } + $this->db->sql_freeresult($result); + } + + if (sizeof($id_ary)) + { + $this->save_ids($search_key, '', $author_ary, $total_results, $id_ary, $start, $sort_dir); + $id_ary = array_slice($id_ary, 0, $per_page); + + return $total_results; + } + return false; + } + + /** + * Split a text into words of a given length + * + * The text is converted to UTF-8, cleaned up, and split. Then, words that + * conform to the defined length range are returned in an array. + * + * NOTE: duplicates are NOT removed from the return array + * + * @param string $text Text to split, encoded in UTF-8 + * @return array Array of UTF-8 words + */ + public function split_message($text) + { + $match = $words = array(); + + /** + * Taken from the original code + */ + // Do not index code + $match[] = '#\[code(?:=.*?)?(\:?[0-9a-z]{5,})\].*?\[\/code(\:?[0-9a-z]{5,})\]#is'; + // BBcode + $match[] = '#\[\/?[a-z0-9\*\+\-]+(?:=.*?)?(?::[a-z])?(\:?[0-9a-z]{5,})\]#'; + + $min = $this->word_length['min']; + $max = $this->word_length['max']; + + $isset_min = $min - 1; + + /** + * Clean up the string, remove HTML tags, remove BBCodes + */ + $word = strtok($this->cleanup(preg_replace($match, ' ', strip_tags($text)), -1), ' '); + + while (strlen($word)) + { + if (strlen($word) > 255 || strlen($word) <= $isset_min) + { + /** + * Words longer than 255 bytes are ignored. This will have to be + * changed whenever we change the length of search_wordlist.word_text + * + * Words shorter than $isset_min bytes are ignored, too + */ + $word = strtok(' '); + continue; + } + + $len = utf8_strlen($word); + + /** + * Test whether the word is too short to be indexed. + * + * Note that this limit does NOT apply to CJK and Hangul + */ + if ($len < $min) + { + /** + * Note: this could be optimized. If the codepoint is lower than Hangul's range + * we know that it will also be lower than CJK ranges + */ + if ((strncmp($word, UTF8_HANGUL_FIRST, 3) < 0 || strncmp($word, UTF8_HANGUL_LAST, 3) > 0) + && (strncmp($word, UTF8_CJK_FIRST, 3) < 0 || strncmp($word, UTF8_CJK_LAST, 3) > 0) + && (strncmp($word, UTF8_CJK_B_FIRST, 4) < 0 || strncmp($word, UTF8_CJK_B_LAST, 4) > 0)) + { + $word = strtok(' '); + continue; + } + } + + $words[] = $word; + $word = strtok(' '); + } + + return $words; + } + + /** + * Updates wordlist and wordmatch tables when a message is posted or changed + * + * @param string $mode Contains the post mode: edit, post, reply, quote + * @param int $post_id The id of the post which is modified/created + * @param string &$message New or updated post content + * @param string &$subject New or updated post subject + * @param int $poster_id Post author's user id + * @param int $forum_id The id of the forum in which the post is located + */ + public function index($mode, $post_id, &$message, &$subject, $poster_id, $forum_id) + { + if (!$this->config['fulltext_native_load_upd']) + { + /** + * The search indexer is disabled, return + */ + return; + } + + // Split old and new post/subject to obtain array of 'words' + $split_text = $this->split_message($message); + $split_title = $this->split_message($subject); + + $cur_words = array('post' => array(), 'title' => array()); + + $words = array(); + if ($mode == 'edit') + { + $words['add']['post'] = array(); + $words['add']['title'] = array(); + $words['del']['post'] = array(); + $words['del']['title'] = array(); + + $sql = 'SELECT w.word_id, w.word_text, m.title_match + FROM ' . SEARCH_WORDLIST_TABLE . ' w, ' . SEARCH_WORDMATCH_TABLE . " m + WHERE m.post_id = $post_id + AND w.word_id = m.word_id"; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $which = ($row['title_match']) ? 'title' : 'post'; + $cur_words[$which][$row['word_text']] = $row['word_id']; + } + $this->db->sql_freeresult($result); + + $words['add']['post'] = array_diff($split_text, array_keys($cur_words['post'])); + $words['add']['title'] = array_diff($split_title, array_keys($cur_words['title'])); + $words['del']['post'] = array_diff(array_keys($cur_words['post']), $split_text); + $words['del']['title'] = array_diff(array_keys($cur_words['title']), $split_title); + } + else + { + $words['add']['post'] = $split_text; + $words['add']['title'] = $split_title; + $words['del']['post'] = array(); + $words['del']['title'] = array(); + } + unset($split_text); + unset($split_title); + + // Get unique words from the above arrays + $unique_add_words = array_unique(array_merge($words['add']['post'], $words['add']['title'])); + + // We now have unique arrays of all words to be added and removed and + // individual arrays of added and removed words for text and title. What + // we need to do now is add the new words (if they don't already exist) + // and then add (or remove) matches between the words and this post + if (sizeof($unique_add_words)) + { + $sql = 'SELECT word_id, word_text + FROM ' . SEARCH_WORDLIST_TABLE . ' + WHERE ' . $this->db->sql_in_set('word_text', $unique_add_words); + $result = $this->db->sql_query($sql); + + $word_ids = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $word_ids[$row['word_text']] = $row['word_id']; + } + $this->db->sql_freeresult($result); + $new_words = array_diff($unique_add_words, array_keys($word_ids)); + + $this->db->sql_transaction('begin'); + if (sizeof($new_words)) + { + $sql_ary = array(); + + foreach ($new_words as $word) + { + $sql_ary[] = array('word_text' => (string) $word, 'word_count' => 0); + } + $this->db->sql_return_on_error(true); + $this->db->sql_multi_insert(SEARCH_WORDLIST_TABLE, $sql_ary); + $this->db->sql_return_on_error(false); + } + unset($new_words, $sql_ary); + } + else + { + $this->db->sql_transaction('begin'); + } + + // now update the search match table, remove links to removed words and add links to new words + foreach ($words['del'] as $word_in => $word_ary) + { + $title_match = ($word_in == 'title') ? 1 : 0; + + if (sizeof($word_ary)) + { + $sql_in = array(); + foreach ($word_ary as $word) + { + $sql_in[] = $cur_words[$word_in][$word]; + } + + $sql = 'DELETE FROM ' . SEARCH_WORDMATCH_TABLE . ' + WHERE ' . $this->db->sql_in_set('word_id', $sql_in) . ' + AND post_id = ' . intval($post_id) . " + AND title_match = $title_match"; + $this->db->sql_query($sql); + + $sql = 'UPDATE ' . SEARCH_WORDLIST_TABLE . ' + SET word_count = word_count - 1 + WHERE ' . $this->db->sql_in_set('word_id', $sql_in) . ' + AND word_count > 0'; + $this->db->sql_query($sql); + + unset($sql_in); + } + } + + $this->db->sql_return_on_error(true); + foreach ($words['add'] as $word_in => $word_ary) + { + $title_match = ($word_in == 'title') ? 1 : 0; + + if (sizeof($word_ary)) + { + $sql = 'INSERT INTO ' . SEARCH_WORDMATCH_TABLE . ' (post_id, word_id, title_match) + SELECT ' . (int) $post_id . ', word_id, ' . (int) $title_match . ' + FROM ' . SEARCH_WORDLIST_TABLE . ' + WHERE ' . $this->db->sql_in_set('word_text', $word_ary); + $this->db->sql_query($sql); + + $sql = 'UPDATE ' . SEARCH_WORDLIST_TABLE . ' + SET word_count = word_count + 1 + WHERE ' . $this->db->sql_in_set('word_text', $word_ary); + $this->db->sql_query($sql); + } + } + $this->db->sql_return_on_error(false); + + $this->db->sql_transaction('commit'); + + // destroy cached search results containing any of the words removed or added + $this->destroy_cache(array_unique(array_merge($words['add']['post'], $words['add']['title'], $words['del']['post'], $words['del']['title'])), array($poster_id)); + + unset($unique_add_words); + unset($words); + unset($cur_words); + } + + /** + * Removes entries from the wordmatch table for the specified post_ids + */ + public function index_remove($post_ids, $author_ids, $forum_ids) + { + if (sizeof($post_ids)) + { + $sql = 'SELECT w.word_id, w.word_text, m.title_match + FROM ' . SEARCH_WORDMATCH_TABLE . ' m, ' . SEARCH_WORDLIST_TABLE . ' w + WHERE ' . $this->db->sql_in_set('m.post_id', $post_ids) . ' + AND w.word_id = m.word_id'; + $result = $this->db->sql_query($sql); + + $message_word_ids = $title_word_ids = $word_texts = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + if ($row['title_match']) + { + $title_word_ids[] = $row['word_id']; + } + else + { + $message_word_ids[] = $row['word_id']; + } + $word_texts[] = $row['word_text']; + } + $this->db->sql_freeresult($result); + + if (sizeof($title_word_ids)) + { + $sql = 'UPDATE ' . SEARCH_WORDLIST_TABLE . ' + SET word_count = word_count - 1 + WHERE ' . $this->db->sql_in_set('word_id', $title_word_ids) . ' + AND word_count > 0'; + $this->db->sql_query($sql); + } + + if (sizeof($message_word_ids)) + { + $sql = 'UPDATE ' . SEARCH_WORDLIST_TABLE . ' + SET word_count = word_count - 1 + WHERE ' . $this->db->sql_in_set('word_id', $message_word_ids) . ' + AND word_count > 0'; + $this->db->sql_query($sql); + } + + unset($title_word_ids); + unset($message_word_ids); + + $sql = 'DELETE FROM ' . SEARCH_WORDMATCH_TABLE . ' + WHERE ' . $this->db->sql_in_set('post_id', $post_ids); + $this->db->sql_query($sql); + } + + $this->destroy_cache(array_unique($word_texts), array_unique($author_ids)); + } + + /** + * Tidy up indexes: Tag 'common words' and remove + * words no longer referenced in the match table + */ + public function tidy() + { + // Is the fulltext indexer disabled? If yes then we need not + // carry on ... it's okay ... I know when I'm not wanted boo hoo + if (!$this->config['fulltext_native_load_upd']) + { + set_config('search_last_gc', time(), true); + return; + } + + $destroy_cache_words = array(); + + // Remove common words + if ($this->config['num_posts'] >= 100 && $this->config['fulltext_native_common_thres']) + { + $common_threshold = ((double) $this->config['fulltext_native_common_thres']) / 100.0; + // First, get the IDs of common words + $sql = 'SELECT word_id, word_text + FROM ' . SEARCH_WORDLIST_TABLE . ' + WHERE word_count > ' . floor($this->config['num_posts'] * $common_threshold) . ' + OR word_common = 1'; + $result = $this->db->sql_query($sql); + + $sql_in = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $sql_in[] = $row['word_id']; + $destroy_cache_words[] = $row['word_text']; + } + $this->db->sql_freeresult($result); + + if (sizeof($sql_in)) + { + // Flag the words + $sql = 'UPDATE ' . SEARCH_WORDLIST_TABLE . ' + SET word_common = 1 + WHERE ' . $this->db->sql_in_set('word_id', $sql_in); + $this->db->sql_query($sql); + + // by setting search_last_gc to the new time here we make sure that if a user reloads because the + // following query takes too long, he won't run into it again + set_config('search_last_gc', time(), true); + + // Delete the matches + $sql = 'DELETE FROM ' . SEARCH_WORDMATCH_TABLE . ' + WHERE ' . $this->db->sql_in_set('word_id', $sql_in); + $this->db->sql_query($sql); + } + unset($sql_in); + } + + if (sizeof($destroy_cache_words)) + { + // destroy cached search results containing any of the words that are now common or were removed + $this->destroy_cache(array_unique($destroy_cache_words)); + } + + set_config('search_last_gc', time(), true); + } + + /** + * Deletes all words from the index + */ + public function delete_index($acp_module, $u_action) + { + switch ($this->db->sql_layer) + { + case 'sqlite': + case 'firebird': + $this->db->sql_query('DELETE FROM ' . SEARCH_WORDLIST_TABLE); + $this->db->sql_query('DELETE FROM ' . SEARCH_WORDMATCH_TABLE); + $this->db->sql_query('DELETE FROM ' . SEARCH_RESULTS_TABLE); + break; + + default: + $this->db->sql_query('TRUNCATE TABLE ' . SEARCH_WORDLIST_TABLE); + $this->db->sql_query('TRUNCATE TABLE ' . SEARCH_WORDMATCH_TABLE); + $this->db->sql_query('TRUNCATE TABLE ' . SEARCH_RESULTS_TABLE); + break; + } + } + + /** + * Returns true if both FULLTEXT indexes exist + */ + public function index_created() + { + if (!sizeof($this->stats)) + { + $this->get_stats(); + } + + return ($this->stats['total_words'] && $this->stats['total_matches']) ? true : false; + } + + /** + * Returns an associative array containing information about the indexes + */ + public function index_stats() + { + if (!sizeof($this->stats)) + { + $this->get_stats(); + } + + return array( + $this->user->lang['TOTAL_WORDS'] => $this->stats['total_words'], + $this->user->lang['TOTAL_MATCHES'] => $this->stats['total_matches']); + } + + protected function get_stats() + { + $this->stats['total_words'] = $this->db->get_estimated_row_count(SEARCH_WORDLIST_TABLE); + $this->stats['total_matches'] = $this->db->get_estimated_row_count(SEARCH_WORDMATCH_TABLE); + } + + /** + * Clean up a text to remove non-alphanumeric characters + * + * This method receives a UTF-8 string, normalizes and validates it, replaces all + * non-alphanumeric characters with strings then returns the result. + * + * Any number of "allowed chars" can be passed as a UTF-8 string in NFC. + * + * @param string $text Text to split, in UTF-8 (not normalized or sanitized) + * @param string $allowed_chars String of special chars to allow + * @param string $encoding Text encoding + * @return string Cleaned up text, only alphanumeric chars are left + * + * @todo normalizer::cleanup being able to be used? + */ + protected function cleanup($text, $allowed_chars = null, $encoding = 'utf-8') + { + static $conv = array(), $conv_loaded = array(); + $words = $allow = array(); + + // Convert the text to UTF-8 + $encoding = strtolower($encoding); + if ($encoding != 'utf-8') + { + $text = utf8_recode($text, $encoding); + } + + $utf_len_mask = array( + "\xC0" => 2, + "\xD0" => 2, + "\xE0" => 3, + "\xF0" => 4 + ); + + /** + * Replace HTML entities and NCRs + */ + $text = htmlspecialchars_decode(utf8_decode_ncr($text), ENT_QUOTES); + + /** + * Load the UTF-8 normalizer + * + * If we use it more widely, an instance of that class should be held in a + * a global variable instead + */ + utf_normalizer::nfc($text); + + /** + * The first thing we do is: + * + * - convert ASCII-7 letters to lowercase + * - remove the ASCII-7 non-alpha characters + * - remove the bytes that should not appear in a valid UTF-8 string: 0xC0, + * 0xC1 and 0xF5-0xFF + * + * @todo in theory, the third one is already taken care of during normalization and those chars should have been replaced by Unicode replacement chars + */ + $sb_match = "ISTCPAMELRDOJBNHFGVWUQKYXZ\r\n\t!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F\xC0\xC1\xF5\xF6\xF7\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xFF"; + $sb_replace = 'istcpamelrdojbnhfgvwuqkyxz '; + + /** + * This is the list of legal ASCII chars, it is automatically extended + * with ASCII chars from $allowed_chars + */ + $legal_ascii = ' eaisntroludcpmghbfvq10xy2j9kw354867z'; + + /** + * Prepare an array containing the extra chars to allow + */ + if (isset($allowed_chars[0])) + { + $pos = 0; + $len = strlen($allowed_chars); + do + { + $c = $allowed_chars[$pos]; + + if ($c < "\x80") + { + /** + * ASCII char + */ + $sb_pos = strpos($sb_match, $c); + if (is_int($sb_pos)) + { + /** + * Remove the char from $sb_match and its corresponding + * replacement in $sb_replace + */ + $sb_match = substr($sb_match, 0, $sb_pos) . substr($sb_match, $sb_pos + 1); + $sb_replace = substr($sb_replace, 0, $sb_pos) . substr($sb_replace, $sb_pos + 1); + $legal_ascii .= $c; + } + + ++$pos; + } + else + { + /** + * UTF-8 char + */ + $utf_len = $utf_len_mask[$c & "\xF0"]; + $allow[substr($allowed_chars, $pos, $utf_len)] = 1; + $pos += $utf_len; + } + } + while ($pos < $len); + } + + $text = strtr($text, $sb_match, $sb_replace); + $ret = ''; + + $pos = 0; + $len = strlen($text); + + do + { + /** + * Do all consecutive ASCII chars at once + */ + if ($spn = strspn($text, $legal_ascii, $pos)) + { + $ret .= substr($text, $pos, $spn); + $pos += $spn; + } + + if ($pos >= $len) + { + return $ret; + } + + /** + * Capture the UTF char + */ + $utf_len = $utf_len_mask[$text[$pos] & "\xF0"]; + $utf_char = substr($text, $pos, $utf_len); + $pos += $utf_len; + + if (($utf_char >= UTF8_HANGUL_FIRST && $utf_char <= UTF8_HANGUL_LAST) + || ($utf_char >= UTF8_CJK_FIRST && $utf_char <= UTF8_CJK_LAST) + || ($utf_char >= UTF8_CJK_B_FIRST && $utf_char <= UTF8_CJK_B_LAST)) + { + /** + * All characters within these ranges are valid + * + * We separate them with a space in order to index each character + * individually + */ + $ret .= ' ' . $utf_char . ' '; + continue; + } + + if (isset($allow[$utf_char])) + { + /** + * The char is explicitly allowed + */ + $ret .= $utf_char; + continue; + } + + if (isset($conv[$utf_char])) + { + /** + * The char is mapped to something, maybe to itself actually + */ + $ret .= $conv[$utf_char]; + continue; + } + + /** + * The char isn't mapped, but did we load its conversion table? + * + * The search indexer table is split into blocks. The block number of + * each char is equal to its codepoint right-shifted for 11 bits. It + * means that out of the 11, 16 or 21 meaningful bits of a 2-, 3- or + * 4- byte sequence we only keep the leftmost 0, 5 or 10 bits. Thus, + * all UTF chars encoded in 2 bytes are in the same first block. + */ + if (isset($utf_char[2])) + { + if (isset($utf_char[3])) + { + /** + * 1111 0nnn 10nn nnnn 10nx xxxx 10xx xxxx + * 0000 0111 0011 1111 0010 0000 + */ + $idx = ((ord($utf_char[0]) & 0x07) << 7) | ((ord($utf_char[1]) & 0x3F) << 1) | ((ord($utf_char[2]) & 0x20) >> 5); + } + else + { + /** + * 1110 nnnn 10nx xxxx 10xx xxxx + * 0000 0111 0010 0000 + */ + $idx = ((ord($utf_char[0]) & 0x07) << 1) | ((ord($utf_char[1]) & 0x20) >> 5); + } + } + else + { + /** + * 110x xxxx 10xx xxxx + * 0000 0000 0000 0000 + */ + $idx = 0; + } + + /** + * Check if the required conv table has been loaded already + */ + if (!isset($conv_loaded[$idx])) + { + $conv_loaded[$idx] = 1; + $file = $this->phpbb_root_path . 'includes/utf/data/search_indexer_' . $idx . '.' . $this->php_ext; + + if (file_exists($file)) + { + $conv += include($file); + } + } + + if (isset($conv[$utf_char])) + { + $ret .= $conv[$utf_char]; + } + else + { + /** + * We add an entry to the conversion table so that we + * don't have to convert to codepoint and perform the checks + * that are above this block + */ + $conv[$utf_char] = ' '; + $ret .= ' '; + } + } + while (1); + + return $ret; + } + + /** + * Returns a list of options for the ACP to display + */ + public function acp() + { + /** + * if we need any options, copied from fulltext_native for now, will have to be adjusted or removed + */ + + $tpl = ' +
+

' . $this->user->lang['YES_SEARCH_UPDATE_EXPLAIN'] . '
+
+
+
+

' . $this->user->lang['MIN_SEARCH_CHARS_EXPLAIN'] . '
+
+
+
+

' . $this->user->lang['MAX_SEARCH_CHARS_EXPLAIN'] . '
+
+
+
+

' . $this->user->lang['COMMON_WORD_THRESHOLD_EXPLAIN'] . '
+
%
+
+ '; + + // These are fields required in the config table + return array( + 'tpl' => $tpl, + 'config' => array('fulltext_native_load_upd' => 'bool', 'fulltext_native_min_chars' => 'integer:0:255', 'fulltext_native_max_chars' => 'integer:0:255', 'fulltext_native_common_thres' => 'double:0:100') + ); + } +} diff --git a/phpBB/phpbb/search/fulltext_postgres.php b/phpBB/phpbb/search/fulltext_postgres.php new file mode 100644 index 0000000000..6b4b310f2e --- /dev/null +++ b/phpBB/phpbb/search/fulltext_postgres.php @@ -0,0 +1,963 @@ +config = $config; + $this->db = $db; + $this->user = $user; + + $this->word_length = array('min' => $this->config['fulltext_postgres_min_word_len'], 'max' => $this->config['fulltext_postgres_max_word_len']); + + if ($this->db->sql_layer == 'postgres') + { + $pgsql_version = explode(',', substr($this->db->sql_server_info(), 10)); + $this->version = trim($pgsql_version[0]); + if (version_compare($this->version, '8.3', '>=')) + { + $this->tsearch_usable = true; + } + } + + /** + * Load the UTF tools + */ + if (!function_exists('utf8_strlen')) + { + include($phpbb_root_path . 'includes/utf/utf_tools.' . $phpEx); + } + + $error = false; + } + + /** + * Returns the name of this search backend to be displayed to administrators + * + * @return string Name + */ + public function get_name() + { + return 'PostgreSQL Fulltext'; + } + + /** + * Returns the search_query + * + * @return string search query + */ + public function get_search_query() + { + return $this->search_query; + } + + /** + * Returns the common_words array + * + * @return array common words that are ignored by search backend + */ + public function get_common_words() + { + return $this->common_words; + } + + /** + * Returns the word_length array + * + * @return array min and max word length for searching + */ + public function get_word_length() + { + return $this->word_length; + } + + /** + * Returns if phrase search is supported or not + * + * @return bool + */ + public function supports_phrase_search() + { + return $this->phrase_search; + } + + /** + * Checks for correct PostgreSQL version and stores min/max word length in the config + * + * @return string|bool Language key of the error/incompatiblity occurred + */ + public function init() + { + if ($this->db->sql_layer != 'postgres') + { + return $this->user->lang['FULLTEXT_POSTGRES_INCOMPATIBLE_DATABASE']; + } + + if (!$this->tsearch_usable) + { + return $this->user->lang['FULLTEXT_POSTGRES_TS_NOT_USABLE']; + } + + return false; + } + + /** + * Splits keywords entered by a user into an array of words stored in $this->split_words + * Stores the tidied search query in $this->search_query + * + * @param string &$keywords Contains the keyword as entered by the user + * @param string $terms is either 'all' or 'any' + * @return bool false if no valid keywords were found and otherwise true + */ + public function split_keywords(&$keywords, $terms) + { + if ($terms == 'all') + { + $match = array('#\sand\s#iu', '#\sor\s#iu', '#\snot\s#iu', '#(^|\s)\+#', '#(^|\s)-#', '#(^|\s)\|#'); + $replace = array(' +', ' |', ' -', ' +', ' -', ' |'); + + $keywords = preg_replace($match, $replace, $keywords); + } + + // Filter out as above + $split_keywords = preg_replace("#[\"\n\r\t]+#", ' ', trim(htmlspecialchars_decode($keywords))); + + // Split words + $split_keywords = preg_replace('#([^\p{L}\p{N}\'*"()])#u', '$1$1', str_replace('\'\'', '\' \'', trim($split_keywords))); + $matches = array(); + preg_match_all('#(?:[^\p{L}\p{N}*"()]|^)([+\-|]?(?:[\p{L}\p{N}*"()]+\'?)*[\p{L}\p{N}*"()])(?:[^\p{L}\p{N}*"()]|$)#u', $split_keywords, $matches); + $this->split_words = $matches[1]; + + foreach ($this->split_words as $i => $word) + { + $clean_word = preg_replace('#^[+\-|"]#', '', $word); + + // check word length + $clean_len = utf8_strlen(str_replace('*', '', $clean_word)); + if (($clean_len < $this->config['fulltext_postgres_min_word_len']) || ($clean_len > $this->config['fulltext_postgres_max_word_len'])) + { + $this->common_words[] = $word; + unset($this->split_words[$i]); + } + } + + if ($terms == 'any') + { + $this->search_query = ''; + $this->tsearch_query = ''; + foreach ($this->split_words as $word) + { + if ((strpos($word, '+') === 0) || (strpos($word, '-') === 0) || (strpos($word, '|') === 0)) + { + $word = substr($word, 1); + } + $this->search_query .= $word . ' '; + $this->tsearch_query .= '|' . $word . ' '; + } + } + else + { + $this->search_query = ''; + $this->tsearch_query = ''; + foreach ($this->split_words as $word) + { + if (strpos($word, '+') === 0) + { + $this->search_query .= $word . ' '; + $this->tsearch_query .= '&' . substr($word, 1) . ' '; + } + elseif (strpos($word, '-') === 0) + { + $this->search_query .= $word . ' '; + $this->tsearch_query .= '&!' . substr($word, 1) . ' '; + } + elseif (strpos($word, '|') === 0) + { + $this->search_query .= $word . ' '; + $this->tsearch_query .= '|' . substr($word, 1) . ' '; + } + else + { + $this->search_query .= '+' . $word . ' '; + $this->tsearch_query .= '&' . $word . ' '; + } + } + } + + $this->tsearch_query = substr($this->tsearch_query, 1); + $this->search_query = utf8_htmlspecialchars($this->search_query); + + if ($this->search_query) + { + $this->split_words = array_values($this->split_words); + sort($this->split_words); + return true; + } + return false; + } + + /** + * Turns text into an array of words + * @param string $text contains post text/subject + */ + public function split_message($text) + { + // Split words + $text = preg_replace('#([^\p{L}\p{N}\'*])#u', '$1$1', str_replace('\'\'', '\' \'', trim($text))); + $matches = array(); + preg_match_all('#(?:[^\p{L}\p{N}*]|^)([+\-|]?(?:[\p{L}\p{N}*]+\'?)*[\p{L}\p{N}*])(?:[^\p{L}\p{N}*]|$)#u', $text, $matches); + $text = $matches[1]; + + // remove too short or too long words + $text = array_values($text); + for ($i = 0, $n = sizeof($text); $i < $n; $i++) + { + $text[$i] = trim($text[$i]); + if (utf8_strlen($text[$i]) < $this->config['fulltext_postgres_min_word_len'] || utf8_strlen($text[$i]) > $this->config['fulltext_postgres_max_word_len']) + { + unset($text[$i]); + } + } + + return array_values($text); + } + + /** + * Performs a search on keywords depending on display specific params. You have to run split_keywords() first + * + * @param string $type contains either posts or topics depending on what should be searched for + * @param string $fields contains either titleonly (topic titles should be searched), msgonly (only message bodies should be searched), firstpost (only subject and body of the first post should be searched) or all (all post bodies and subjects should be searched) + * @param string $terms is either 'all' (use query as entered, words without prefix should default to "have to be in field") or 'any' (ignore search query parts and just return all posts that contain any of the specified words) + * @param array $sort_by_sql contains SQL code for the ORDER BY part of a query + * @param string $sort_key is the key of $sort_by_sql for the selected sorting + * @param string $sort_dir is either a or d representing ASC and DESC + * @param string $sort_days specifies the maximum amount of days a post may be old + * @param array $ex_fid_ary specifies an array of forum ids which should not be searched + * @param string $post_visibility specifies which types of posts the user can view in which forums + * @param int $topic_id is set to 0 or a topic id, if it is not 0 then only posts in this topic should be searched + * @param array $author_ary an array of author ids if the author should be ignored during the search the array is empty + * @param string $author_name specifies the author match, when ANONYMOUS is also a search-match + * @param array &$id_ary passed by reference, to be filled with ids for the page specified by $start and $per_page, should be ordered + * @param int $start indicates the first index of the page + * @param int $per_page number of ids each page is supposed to contain + * @return boolean|int total number of results + */ + public function keyword_search($type, $fields, $terms, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $post_visibility, $topic_id, $author_ary, $author_name, &$id_ary, &$start, $per_page) + { + // No keywords? No posts + if (!$this->search_query) + { + return false; + } + + // When search query contains queries like -foo + if (strpos($this->search_query, '+') === false) + { + return false; + } + + // generate a search_key from all the options to identify the results + $search_key = md5(implode('#', array( + implode(', ', $this->split_words), + $type, + $fields, + $terms, + $sort_days, + $sort_key, + $topic_id, + implode(',', $ex_fid_ary), + $post_visibility, + implode(',', $author_ary) + ))); + + if ($start < 0) + { + $start = 0; + } + + // try reading the results from cache + $result_count = 0; + if ($this->obtain_ids($search_key, $result_count, $id_ary, $start, $per_page, $sort_dir) == SEARCH_RESULT_IN_CACHE) + { + return $result_count; + } + + $id_ary = array(); + + $join_topic = ($type == 'posts') ? false : true; + + // Build sql strings for sorting + $sql_sort = $sort_by_sql[$sort_key] . (($sort_dir == 'a') ? ' ASC' : ' DESC'); + $sql_sort_table = $sql_sort_join = ''; + + switch ($sql_sort[0]) + { + case 'u': + $sql_sort_table = USERS_TABLE . ' u, '; + $sql_sort_join = ($type == 'posts') ? ' AND u.user_id = p.poster_id ' : ' AND u.user_id = t.topic_poster '; + break; + + case 't': + $join_topic = true; + break; + + case 'f': + $sql_sort_table = FORUMS_TABLE . ' f, '; + $sql_sort_join = ' AND f.forum_id = p.forum_id '; + break; + } + + // Build some display specific sql strings + switch ($fields) + { + case 'titleonly': + $sql_match = 'p.post_subject'; + $sql_match_where = ' AND p.post_id = t.topic_first_post_id'; + $join_topic = true; + break; + + case 'msgonly': + $sql_match = 'p.post_text'; + $sql_match_where = ''; + break; + + case 'firstpost': + $sql_match = 'p.post_subject, p.post_text'; + $sql_match_where = ' AND p.post_id = t.topic_first_post_id'; + $join_topic = true; + break; + + default: + $sql_match = 'p.post_subject, p.post_text'; + $sql_match_where = ''; + break; + } + + $sql_select = ($type == 'posts') ? 'p.post_id' : 'DISTINCT t.topic_id'; + $sql_from = ($join_topic) ? TOPICS_TABLE . ' t, ' : ''; + $field = ($type == 'posts') ? 'post_id' : 'topic_id'; + $sql_author = (sizeof($author_ary) == 1) ? ' = ' . $author_ary[0] : 'IN (' . implode(', ', $author_ary) . ')'; + + if (sizeof($author_ary) && $author_name) + { + // first one matches post of registered users, second one guests and deleted users + $sql_author = '(' . $this->db->sql_in_set('p.poster_id', array_diff($author_ary, array(ANONYMOUS)), false, true) . ' OR p.post_username ' . $author_name . ')'; + } + else if (sizeof($author_ary)) + { + $sql_author = ' AND ' . $this->db->sql_in_set('p.poster_id', $author_ary); + } + else + { + $sql_author = ''; + } + + $sql_where_options = $sql_sort_join; + $sql_where_options .= ($topic_id) ? ' AND p.topic_id = ' . $topic_id : ''; + $sql_where_options .= ($join_topic) ? ' AND t.topic_id = p.topic_id' : ''; + $sql_where_options .= (sizeof($ex_fid_ary)) ? ' AND ' . $this->db->sql_in_set('p.forum_id', $ex_fid_ary, true) : ''; + $sql_where_options .= ' AND ' . $post_visibility; + $sql_where_options .= $sql_author; + $sql_where_options .= ($sort_days) ? ' AND p.post_time >= ' . (time() - ($sort_days * 86400)) : ''; + $sql_where_options .= $sql_match_where; + + $tmp_sql_match = array(); + foreach (explode(',', $sql_match) as $sql_match_column) + { + $tmp_sql_match[] = "to_tsvector ('" . $this->db->sql_escape($this->config['fulltext_postgres_ts_name']) . "', " . $sql_match_column . ") @@ to_tsquery ('" . $this->db->sql_escape($this->config['fulltext_postgres_ts_name']) . "', '" . $this->db->sql_escape($this->tsearch_query) . "')"; + } + + $this->db->sql_transaction('begin'); + + $sql_from = "FROM $sql_from$sql_sort_table" . POSTS_TABLE . " p"; + $sql_where = "WHERE (" . implode(' OR ', $tmp_sql_match) . ") + $sql_where_options"; + $sql = "SELECT $sql_select + $sql_from + $sql_where + ORDER BY $sql_sort"; + $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); + + while ($row = $this->db->sql_fetchrow($result)) + { + $id_ary[] = $row[$field]; + } + $this->db->sql_freeresult($result); + + $id_ary = array_unique($id_ary); + + // if the total result count is not cached yet, retrieve it from the db + if (!$result_count) + { + $sql_count = "SELECT COUNT(*) as result_count + $sql_from + $sql_where"; + $result = $this->db->sql_query($sql_count); + $result_count = (int) $this->db->sql_fetchfield('result_count'); + $this->db->sql_freeresult($result); + + if (!$result_count) + { + return false; + } + } + + $this->db->sql_transaction('commit'); + + if ($start >= $result_count) + { + $start = floor(($result_count - 1) / $per_page) * $per_page; + + $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); + + while ($row = $this->db->sql_fetchrow($result)) + { + $id_ary[] = $row[$field]; + } + $this->db->sql_freeresult($result); + + $id_ary = array_unique($id_ary); + } + + // store the ids, from start on then delete anything that isn't on the current page because we only need ids for one page + $this->save_ids($search_key, implode(' ', $this->split_words), $author_ary, $result_count, $id_ary, $start, $sort_dir); + $id_ary = array_slice($id_ary, 0, (int) $per_page); + + return $result_count; + } + + /** + * Performs a search on an author's posts without caring about message contents. Depends on display specific params + * + * @param string $type contains either posts or topics depending on what should be searched for + * @param boolean $firstpost_only if true, only topic starting posts will be considered + * @param array $sort_by_sql contains SQL code for the ORDER BY part of a query + * @param string $sort_key is the key of $sort_by_sql for the selected sorting + * @param string $sort_dir is either a or d representing ASC and DESC + * @param string $sort_days specifies the maximum amount of days a post may be old + * @param array $ex_fid_ary specifies an array of forum ids which should not be searched + * @param string $post_visibility specifies which types of posts the user can view in which forums + * @param int $topic_id is set to 0 or a topic id, if it is not 0 then only posts in this topic should be searched + * @param array $author_ary an array of author ids + * @param string $author_name specifies the author match, when ANONYMOUS is also a search-match + * @param array &$id_ary passed by reference, to be filled with ids for the page specified by $start and $per_page, should be ordered + * @param int $start indicates the first index of the page + * @param int $per_page number of ids each page is supposed to contain + * @return boolean|int total number of results + */ + public function author_search($type, $firstpost_only, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $post_visibility, $topic_id, $author_ary, $author_name, &$id_ary, &$start, $per_page) + { + // No author? No posts + if (!sizeof($author_ary)) + { + return 0; + } + + // generate a search_key from all the options to identify the results + $search_key = md5(implode('#', array( + '', + $type, + ($firstpost_only) ? 'firstpost' : '', + '', + '', + $sort_days, + $sort_key, + $topic_id, + implode(',', $ex_fid_ary), + $post_visibility, + implode(',', $author_ary), + $author_name, + ))); + + if ($start < 0) + { + $start = 0; + } + + // try reading the results from cache + $result_count = 0; + if ($this->obtain_ids($search_key, $result_count, $id_ary, $start, $per_page, $sort_dir) == SEARCH_RESULT_IN_CACHE) + { + return $result_count; + } + + $id_ary = array(); + + // Create some display specific sql strings + if ($author_name) + { + // first one matches post of registered users, second one guests and deleted users + $sql_author = '(' . $this->db->sql_in_set('p.poster_id', array_diff($author_ary, array(ANONYMOUS)), false, true) . ' OR p.post_username ' . $author_name . ')'; + } + else + { + $sql_author = $this->db->sql_in_set('p.poster_id', $author_ary); + } + $sql_fora = (sizeof($ex_fid_ary)) ? ' AND ' . $this->db->sql_in_set('p.forum_id', $ex_fid_ary, true) : ''; + $sql_topic_id = ($topic_id) ? ' AND p.topic_id = ' . (int) $topic_id : ''; + $sql_time = ($sort_days) ? ' AND p.post_time >= ' . (time() - ($sort_days * 86400)) : ''; + $sql_firstpost = ($firstpost_only) ? ' AND p.post_id = t.topic_first_post_id' : ''; + + // Build sql strings for sorting + $sql_sort = $sort_by_sql[$sort_key] . (($sort_dir == 'a') ? ' ASC' : ' DESC'); + $sql_sort_table = $sql_sort_join = ''; + switch ($sql_sort[0]) + { + case 'u': + $sql_sort_table = USERS_TABLE . ' u, '; + $sql_sort_join = ($type == 'posts') ? ' AND u.user_id = p.poster_id ' : ' AND u.user_id = t.topic_poster '; + break; + + case 't': + $sql_sort_table = ($type == 'posts' && !$firstpost_only) ? TOPICS_TABLE . ' t, ' : ''; + $sql_sort_join = ($type == 'posts' && !$firstpost_only) ? ' AND t.topic_id = p.topic_id ' : ''; + break; + + case 'f': + $sql_sort_table = FORUMS_TABLE . ' f, '; + $sql_sort_join = ' AND f.forum_id = p.forum_id '; + break; + } + + $m_approve_fid_sql = ' AND ' . $post_visibility; + + // Build the query for really selecting the post_ids + if ($type == 'posts') + { + $sql = "SELECT p.post_id + FROM " . $sql_sort_table . POSTS_TABLE . ' p' . (($firstpost_only) ? ', ' . TOPICS_TABLE . ' t ' : ' ') . " + WHERE $sql_author + $sql_topic_id + $sql_firstpost + $m_approve_fid_sql + $sql_fora + $sql_sort_join + $sql_time + ORDER BY $sql_sort"; + $field = 'post_id'; + } + else + { + $sql = "SELECT t.topic_id + FROM " . $sql_sort_table . TOPICS_TABLE . ' t, ' . POSTS_TABLE . " p + WHERE $sql_author + $sql_topic_id + $sql_firstpost + $m_approve_fid_sql + $sql_fora + AND t.topic_id = p.topic_id + $sql_sort_join + $sql_time + GROUP BY t.topic_id, $sort_by_sql[$sort_key] + ORDER BY $sql_sort"; + $field = 'topic_id'; + } + + $this->db->sql_transaction('begin'); + + // Only read one block of posts from the db and then cache it + $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); + + while ($row = $this->db->sql_fetchrow($result)) + { + $id_ary[] = $row[$field]; + } + $this->db->sql_freeresult($result); + + // retrieve the total result count if needed + if (!$result_count) + { + if ($type == 'posts') + { + $sql_count = "SELECT COUNT(*) as result_count + FROM " . $sql_sort_table . POSTS_TABLE . ' p' . (($firstpost_only) ? ', ' . TOPICS_TABLE . ' t ' : ' ') . " + WHERE $sql_author + $sql_topic_id + $sql_firstpost + $m_approve_fid_sql + $sql_fora + $sql_sort_join + $sql_time"; + } + else + { + $sql_count = "SELECT COUNT(*) as result_count + FROM " . $sql_sort_table . TOPICS_TABLE . ' t, ' . POSTS_TABLE . " p + WHERE $sql_author + $sql_topic_id + $sql_firstpost + $m_approve_fid_sql + $sql_fora + AND t.topic_id = p.topic_id + $sql_sort_join + $sql_time + GROUP BY t.topic_id, $sort_by_sql[$sort_key]"; + } + + $result = $this->db->sql_query($sql_count); + $result_count = (int) $this->db->sql_fetchfield('result_count'); + + if (!$result_count) + { + return false; + } + } + + $this->db->sql_transaction('commit'); + + if ($start >= $result_count) + { + $start = floor(($result_count - 1) / $per_page) * $per_page; + + $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); + while ($row = $this->db->sql_fetchrow($result)) + { + $id_ary[] = (int) $row[$field]; + } + $this->db->sql_freeresult($result); + + $id_ary = array_unique($id_ary); + } + + if (sizeof($id_ary)) + { + $this->save_ids($search_key, '', $author_ary, $result_count, $id_ary, $start, $sort_dir); + $id_ary = array_slice($id_ary, 0, $per_page); + + return $result_count; + } + return false; + } + + /** + * Destroys cached search results, that contained one of the new words in a post so the results won't be outdated + * + * @param string $mode contains the post mode: edit, post, reply, quote ... + * @param int $post_id contains the post id of the post to index + * @param string $message contains the post text of the post + * @param string $subject contains the subject of the post to index + * @param int $poster_id contains the user id of the poster + * @param int $forum_id contains the forum id of parent forum of the post + */ + public function index($mode, $post_id, &$message, &$subject, $poster_id, $forum_id) + { + // Split old and new post/subject to obtain array of words + $split_text = $this->split_message($message); + $split_title = ($subject) ? $this->split_message($subject) : array(); + + $words = array_unique(array_merge($split_text, $split_title)); + + unset($split_text); + unset($split_title); + + // destroy cached search results containing any of the words removed or added + $this->destroy_cache($words, array($poster_id)); + + unset($words); + } + + /** + * Destroy cached results, that might be outdated after deleting a post + */ + public function index_remove($post_ids, $author_ids, $forum_ids) + { + $this->destroy_cache(array(), $author_ids); + } + + /** + * Destroy old cache entries + */ + public function tidy() + { + // destroy too old cached search results + $this->destroy_cache(array()); + + set_config('search_last_gc', time(), true); + } + + /** + * Create fulltext index + * + * @return string|bool error string is returned incase of errors otherwise false + */ + public function create_index($acp_module, $u_action) + { + // Make sure we can actually use PostgreSQL with fulltext indexes + if ($error = $this->init()) + { + return $error; + } + + if (empty($this->stats)) + { + $this->get_stats(); + } + + if (!isset($this->stats['post_subject'])) + { + $this->db->sql_query("CREATE INDEX " . POSTS_TABLE . "_" . $this->config['fulltext_postgres_ts_name'] . "_post_subject ON " . POSTS_TABLE . " USING gin (to_tsvector ('" . $this->db->sql_escape($this->config['fulltext_postgres_ts_name']) . "', post_subject))"); + } + + if (!isset($this->stats['post_text'])) + { + $this->db->sql_query("CREATE INDEX " . POSTS_TABLE . "_" . $this->config['fulltext_postgres_ts_name'] . "_post_text ON " . POSTS_TABLE . " USING gin (to_tsvector ('" . $this->db->sql_escape($this->config['fulltext_postgres_ts_name']) . "', post_text))"); + } + + $this->db->sql_query('TRUNCATE TABLE ' . SEARCH_RESULTS_TABLE); + + return false; + } + + /** + * Drop fulltext index + * + * @return string|bool error string is returned incase of errors otherwise false + */ + public function delete_index($acp_module, $u_action) + { + // Make sure we can actually use PostgreSQL with fulltext indexes + if ($error = $this->init()) + { + return $error; + } + + if (empty($this->stats)) + { + $this->get_stats(); + } + + if (isset($this->stats['post_subject'])) + { + $this->db->sql_query('DROP INDEX ' . $this->stats['post_subject']['relname']); + } + + if (isset($this->stats['post_text'])) + { + $this->db->sql_query('DROP INDEX ' . $this->stats['post_text']['relname']); + } + + $this->db->sql_query('TRUNCATE TABLE ' . SEARCH_RESULTS_TABLE); + + return false; + } + + /** + * Returns true if both FULLTEXT indexes exist + */ + public function index_created() + { + if (empty($this->stats)) + { + $this->get_stats(); + } + + return (isset($this->stats['post_text']) && isset($this->stats['post_subject'])) ? true : false; + } + + /** + * Returns an associative array containing information about the indexes + */ + public function index_stats() + { + if (empty($this->stats)) + { + $this->get_stats(); + } + + return array( + $this->user->lang['FULLTEXT_POSTGRES_TOTAL_POSTS'] => ($this->index_created()) ? $this->stats['total_posts'] : 0, + ); + } + + /** + * Computes the stats and store them in the $this->stats associative array + */ + protected function get_stats() + { + if ($this->db->sql_layer != 'postgres') + { + $this->stats = array(); + return; + } + + $sql = "SELECT c2.relname, pg_catalog.pg_get_indexdef(i.indexrelid, 0, true) AS indexdef + FROM pg_catalog.pg_class c1, pg_catalog.pg_index i, pg_catalog.pg_class c2 + WHERE c1.relname = '" . POSTS_TABLE . "' + AND pg_catalog.pg_table_is_visible(c1.oid) + AND c1.oid = i.indrelid + AND i.indexrelid = c2.oid"; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + // deal with older PostgreSQL versions which didn't use Index_type + if (strpos($row['indexdef'], 'to_tsvector') !== false) + { + if ($row['relname'] == POSTS_TABLE . '_' . $this->config['fulltext_postgres_ts_name'] . '_post_text' || $row['relname'] == POSTS_TABLE . '_post_text') + { + $this->stats['post_text'] = $row; + } + else if ($row['relname'] == POSTS_TABLE . '_' . $this->config['fulltext_postgres_ts_name'] . '_post_subject' || $row['relname'] == POSTS_TABLE . '_post_subject') + { + $this->stats['post_subject'] = $row; + } + } + } + $this->db->sql_freeresult($result); + + $this->stats['total_posts'] = $this->config['num_posts']; + } + + /** + * Display various options that can be configured for the backend from the acp + * + * @return associative array containing template and config variables + */ + public function acp() + { + $tpl = ' +
+

' . $this->user->lang['FULLTEXT_POSTGRES_VERSION_CHECK_EXPLAIN'] . '
+
' . (($this->tsearch_usable) ? $this->user->lang['YES'] : $this->user->lang['NO']) . ' (PostgreSQL ' . $this->version . ')
+
+
+

' . $this->user->lang['FULLTEXT_POSTGRES_TS_NAME_EXPLAIN'] . '
+
+
+
+

' . $this->user->lang['FULLTEXT_POSTGRES_MIN_WORD_LEN_EXPLAIN'] . '
+
+
+
+

' . $this->user->lang['FULLTEXT_POSTGRES_MAX_WORD_LEN_EXPLAIN'] . '
+
+
+ '; + + // These are fields required in the config table + return array( + 'tpl' => $tpl, + 'config' => array('fulltext_postgres_ts_name' => 'string', 'fulltext_postgres_min_word_len' => 'integer:0:255', 'fulltext_postgres_max_word_len' => 'integer:0:255') + ); + } +} diff --git a/phpBB/phpbb/search/fulltext_sphinx.php b/phpBB/phpbb/search/fulltext_sphinx.php new file mode 100644 index 0000000000..2f7b236c78 --- /dev/null +++ b/phpBB/phpbb/search/fulltext_sphinx.php @@ -0,0 +1,917 @@ +phpbb_root_path = $phpbb_root_path; + $this->php_ext = $phpEx; + $this->config = $config; + $this->user = $user; + $this->db = $db; + $this->auth = $auth; + + if (!class_exists('phpbb_db_tools')) + { + require($this->phpbb_root_path . 'includes/db/db_tools.' . $this->php_ext); + } + + // Initialize phpbb_db_tools object + $this->db_tools = new phpbb_db_tools($this->db); + + if(!$this->config['fulltext_sphinx_id']) + { + set_config('fulltext_sphinx_id', unique_id()); + } + $this->id = $this->config['fulltext_sphinx_id']; + $this->indexes = 'index_phpbb_' . $this->id . '_delta;index_phpbb_' . $this->id . '_main'; + + if (!class_exists('SphinxClient')) + { + require($this->phpbb_root_path . 'includes/sphinxapi.' . $this->php_ext); + } + + // Initialize sphinx client + $this->sphinx = new SphinxClient(); + + $this->sphinx->SetServer(($this->config['fulltext_sphinx_host'] ? $this->config['fulltext_sphinx_host'] : 'localhost'), ($this->config['fulltext_sphinx_port'] ? (int) $this->config['fulltext_sphinx_port'] : 9312)); + + $error = false; + } + + /** + * Returns the name of this search backend to be displayed to administrators + * + * @return string Name + */ + public function get_name() + { + return 'Sphinx Fulltext'; + } + + /** + * Returns the search_query + * + * @return string search query + */ + public function get_search_query() + { + return $this->search_query; + } + + /** + * Returns false as there is no word_len array + * + * @return false + */ + public function get_word_length() + { + return false; + } + + /** + * Returns an empty array as there are no common_words + * + * @return array common words that are ignored by search backend + */ + public function get_common_words() + { + return array(); + } + + /** + * Checks permissions and paths, if everything is correct it generates the config file + * + * @return string|bool Language key of the error/incompatiblity encountered, or false if successful + */ + public function init() + { + if ($this->db->sql_layer != 'mysql' && $this->db->sql_layer != 'mysql4' && $this->db->sql_layer != 'mysqli' && $this->db->sql_layer != 'postgres') + { + return $this->user->lang['FULLTEXT_SPHINX_WRONG_DATABASE']; + } + + // Move delta to main index each hour + set_config('search_gc', 3600); + + return false; + } + + /** + * Generates content of sphinx.conf + * + * @return bool True if sphinx.conf content is correctly generated, false otherwise + */ + protected function config_generate() + { + // Check if Database is supported by Sphinx + if ($this->db->sql_layer =='mysql' || $this->db->sql_layer == 'mysql4' || $this->db->sql_layer == 'mysqli') + { + $this->dbtype = 'mysql'; + } + else if ($this->db->sql_layer == 'postgres') + { + $this->dbtype = 'pgsql'; + } + else + { + $this->config_file_data = $this->user->lang('FULLTEXT_SPHINX_WRONG_DATABASE'); + return false; + } + + // Check if directory paths have been filled + if (!$this->config['fulltext_sphinx_data_path']) + { + $this->config_file_data = $this->user->lang('FULLTEXT_SPHINX_NO_CONFIG_DATA'); + return false; + } + + include($this->phpbb_root_path . 'config.' . $this->php_ext); + + /* Now that we're sure everything was entered correctly, + generate a config for the index. We use a config value + fulltext_sphinx_id for this, as it should be unique. */ + $config_object = new phpbb_search_sphinx_config($this->config_file_data); + $config_data = array( + 'source source_phpbb_' . $this->id . '_main' => array( + array('type', $this->dbtype . ' # mysql or pgsql'), + // This config value sql_host needs to be changed incase sphinx and sql are on different servers + array('sql_host', $dbhost . ' # SQL server host sphinx connects to'), + array('sql_user', $dbuser), + array('sql_pass', $dbpasswd), + array('sql_db', $dbname), + array('sql_port', $dbport . ' # optional, default is 3306 for mysql and 5432 for pgsql'), + array('sql_query_pre', 'SET NAMES \'utf8\''), + array('sql_query_pre', 'UPDATE ' . SPHINX_TABLE . ' SET max_doc_id = (SELECT MAX(post_id) FROM ' . POSTS_TABLE . ') WHERE counter_id = 1'), + array('sql_query_range', 'SELECT MIN(post_id), MAX(post_id) FROM ' . POSTS_TABLE . ''), + array('sql_range_step', '5000'), + array('sql_query', 'SELECT + p.post_id AS id, + p.forum_id, + p.topic_id, + p.poster_id, + p.post_visibility, + 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 ' . POSTS_TABLE . ' p, ' . TOPICS_TABLE . ' t + WHERE + p.topic_id = t.topic_id + AND p.post_id >= $start AND p.post_id <= $end'), + array('sql_query_post', ''), + array('sql_query_post_index', 'UPDATE ' . SPHINX_TABLE . ' SET max_doc_id = $maxid WHERE counter_id = 1'), + array('sql_query_info', 'SELECT * FROM ' . POSTS_TABLE . ' WHERE post_id = $id'), + array('sql_attr_uint', 'forum_id'), + array('sql_attr_uint', 'topic_id'), + array('sql_attr_uint', 'poster_id'), + array('sql_attr_uint', 'post_visibility'), + array('sql_attr_bool', 'topic_first_post'), + array('sql_attr_bool', 'deleted'), + array('sql_attr_timestamp' , 'post_time'), + array('sql_attr_timestamp' , 'topic_last_post_time'), + array('sql_attr_str2ordinal', 'post_subject'), + ), + 'source source_phpbb_' . $this->id . '_delta : source_phpbb_' . $this->id . '_main' => array( + array('sql_query_pre', ''), + array('sql_query_range', ''), + array('sql_range_step', ''), + array('sql_query', 'SELECT + p.post_id AS id, + p.forum_id, + p.topic_id, + p.poster_id, + p.post_visibility, + 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 ' . POSTS_TABLE . ' p, ' . TOPICS_TABLE . ' t + WHERE + p.topic_id = t.topic_id + AND p.post_id >= ( SELECT max_doc_id FROM ' . SPHINX_TABLE . ' WHERE counter_id=1 )'), + ), + 'index index_phpbb_' . $this->id . '_main' => array( + array('path', $this->config['fulltext_sphinx_data_path'] . 'index_phpbb_' . $this->id . '_main'), + array('source', 'source_phpbb_' . $this->id . '_main'), + array('docinfo', 'extern'), + array('morphology', 'none'), + array('stopwords', ''), + array('min_word_len', '2'), + array('charset_type', 'utf-8'), + array('charset_table', 'U+FF10..U+FF19->0..9, 0..9, U+FF41..U+FF5A->a..z, U+FF21..U+FF3A->a..z, A..Z->a..z, a..z, U+0149, U+017F, U+0138, U+00DF, U+00FF, U+00C0..U+00D6->U+00E0..U+00F6, U+00E0..U+00F6, U+00D8..U+00DE->U+00F8..U+00FE, U+00F8..U+00FE, U+0100->U+0101, U+0101, U+0102->U+0103, U+0103, U+0104->U+0105, U+0105, U+0106->U+0107, U+0107, U+0108->U+0109, U+0109, U+010A->U+010B, U+010B, U+010C->U+010D, U+010D, U+010E->U+010F, U+010F, U+0110->U+0111, U+0111, U+0112->U+0113, U+0113, U+0114->U+0115, U+0115, U+0116->U+0117, U+0117, U+0118->U+0119, U+0119, U+011A->U+011B, U+011B, U+011C->U+011D, U+011D, U+011E->U+011F, U+011F, U+0130->U+0131, U+0131, U+0132->U+0133, U+0133, U+0134->U+0135, U+0135, U+0136->U+0137, U+0137, U+0139->U+013A, U+013A, U+013B->U+013C, U+013C, U+013D->U+013E, U+013E, U+013F->U+0140, U+0140, U+0141->U+0142, U+0142, U+0143->U+0144, U+0144, U+0145->U+0146, U+0146, U+0147->U+0148, U+0148, U+014A->U+014B, U+014B, U+014C->U+014D, U+014D, U+014E->U+014F, U+014F, U+0150->U+0151, U+0151, U+0152->U+0153, U+0153, U+0154->U+0155, U+0155, U+0156->U+0157, U+0157, U+0158->U+0159, U+0159, U+015A->U+015B, U+015B, U+015C->U+015D, U+015D, U+015E->U+015F, U+015F, U+0160->U+0161, U+0161, U+0162->U+0163, U+0163, U+0164->U+0165, U+0165, U+0166->U+0167, U+0167, U+0168->U+0169, U+0169, U+016A->U+016B, U+016B, U+016C->U+016D, U+016D, U+016E->U+016F, U+016F, U+0170->U+0171, U+0171, U+0172->U+0173, U+0173, U+0174->U+0175, U+0175, U+0176->U+0177, U+0177, U+0178->U+00FF, U+00FF, U+0179->U+017A, U+017A, U+017B->U+017C, U+017C, U+017D->U+017E, U+017E, U+0410..U+042F->U+0430..U+044F, U+0430..U+044F, U+4E00..U+9FFF'), + array('min_prefix_len', '0'), + array('min_infix_len', '0'), + ), + 'index index_phpbb_' . $this->id . '_delta : index_phpbb_' . $this->id . '_main' => array( + array('path', $this->config['fulltext_sphinx_data_path'] . 'index_phpbb_' . $this->id . '_delta'), + array('source', 'source_phpbb_' . $this->id . '_delta'), + ), + 'indexer' => array( + array('mem_limit', $this->config['fulltext_sphinx_indexer_mem_limit'] . 'M'), + ), + 'searchd' => array( + array('compat_sphinxql_magics' , '0'), + array('listen' , ($this->config['fulltext_sphinx_host'] ? $this->config['fulltext_sphinx_host'] : 'localhost') . ':' . ($this->config['fulltext_sphinx_port'] ? $this->config['fulltext_sphinx_port'] : '9312')), + array('log', $this->config['fulltext_sphinx_data_path'] . 'log/searchd.log'), + array('query_log', $this->config['fulltext_sphinx_data_path'] . 'log/sphinx-query.log'), + array('read_timeout', '5'), + array('max_children', '30'), + array('pid_file', $this->config['fulltext_sphinx_data_path'] . 'searchd.pid'), + array('max_matches', (string) SPHINX_MAX_MATCHES), + array('binlog_path', $this->config['fulltext_sphinx_data_path']), + ), + ); + + $non_unique = array('sql_query_pre' => true, 'sql_attr_uint' => true, 'sql_attr_timestamp' => true, 'sql_attr_str2ordinal' => true, 'sql_attr_bool' => true); + $delete = array('sql_group_column' => true, 'sql_date_column' => true, 'sql_str2ordinal_column' => true); + foreach ($config_data as $section_name => $section_data) + { + $section = $config_object->get_section_by_name($section_name); + if (!$section) + { + $section = $config_object->add_section($section_name); + } + + foreach ($delete as $key => $void) + { + $section->delete_variables_by_name($key); + } + + foreach ($non_unique as $key => $void) + { + $section->delete_variables_by_name($key); + } + + foreach ($section_data as $entry) + { + $key = $entry[0]; + $value = $entry[1]; + + if (!isset($non_unique[$key])) + { + $variable = $section->get_variable_by_name($key); + if (!$variable) + { + $variable = $section->create_variable($key, $value); + } + else + { + $variable->set_value($value); + } + } + else + { + $variable = $section->create_variable($key, $value); + } + } + } + $this->config_file_data = $config_object->get_data(); + + return true; + } + + /** + * Splits keywords entered by a user into an array of words stored in $this->split_words + * Stores the tidied search query in $this->search_query + * + * @param string $keywords Contains the keyword as entered by the user + * @param string $terms is either 'all' or 'any' + * @return false if no valid keywords were found and otherwise true + */ + public function split_keywords(&$keywords, $terms) + { + if ($terms == 'all') + { + $match = array('#\sand\s#i', '#\sor\s#i', '#\snot\s#i', '#\+#', '#-#', '#\|#', '#@#'); + $replace = array(' & ', ' | ', ' - ', ' +', ' -', ' |', ''); + + $replacements = 0; + $keywords = preg_replace($match, $replace, $keywords); + $this->sphinx->SetMatchMode(SPH_MATCH_EXTENDED); + } + else + { + $this->sphinx->SetMatchMode(SPH_MATCH_ANY); + } + + // Keep quotes and new lines + $keywords = str_replace(array('"', "\n"), array('"', ' '), trim($keywords)); + + if (strlen($keywords) > 0) + { + $this->search_query = str_replace('"', '"', $keywords); + return true; + } + + return false; + } + + /** + * Performs a search on keywords depending on display specific params. You have to run split_keywords() first + * + * @param string $type contains either posts or topics depending on what should be searched for + * @param string $fields contains either titleonly (topic titles should be searched), msgonly (only message bodies should be searched), firstpost (only subject and body of the first post should be searched) or all (all post bodies and subjects should be searched) + * @param string $terms is either 'all' (use query as entered, words without prefix should default to "have to be in field") or 'any' (ignore search query parts and just return all posts that contain any of the specified words) + * @param array $sort_by_sql contains SQL code for the ORDER BY part of a query + * @param string $sort_key is the key of $sort_by_sql for the selected sorting + * @param string $sort_dir is either a or d representing ASC and DESC + * @param string $sort_days specifies the maximum amount of days a post may be old + * @param array $ex_fid_ary specifies an array of forum ids which should not be searched + * @param string $post_visibility specifies which types of posts the user can view in which forums + * @param int $topic_id is set to 0 or a topic id, if it is not 0 then only posts in this topic should be searched + * @param array $author_ary an array of author ids if the author should be ignored during the search the array is empty + * @param string $author_name specifies the author match, when ANONYMOUS is also a search-match + * @param array &$id_ary passed by reference, to be filled with ids for the page specified by $start and $per_page, should be ordered + * @param int $start indicates the first index of the page + * @param int $per_page number of ids each page is supposed to contain + * @return boolean|int total number of results + */ + public function keyword_search($type, $fields, $terms, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $post_visibility, $topic_id, $author_ary, $author_name, &$id_ary, &$start, $per_page) + { + // No keywords? No posts. + if (!strlen($this->search_query) && !sizeof($author_ary)) + { + return false; + } + + $id_ary = array(); + + $join_topic = ($type != 'posts'); + + // Sorting + + if ($type == 'topics') + { + switch ($sort_key) + { + case 'a': + $this->sphinx->SetGroupBy('topic_id', SPH_GROUPBY_ATTR, 'poster_id ' . (($sort_dir == 'a') ? 'ASC' : 'DESC')); + break; + + case 'f': + $this->sphinx->SetGroupBy('topic_id', SPH_GROUPBY_ATTR, 'forum_id ' . (($sort_dir == 'a') ? 'ASC' : 'DESC')); + break; + + case 'i': + + case 's': + $this->sphinx->SetGroupBy('topic_id', SPH_GROUPBY_ATTR, 'post_subject ' . (($sort_dir == 'a') ? 'ASC' : 'DESC')); + break; + + case 't': + + default: + $this->sphinx->SetGroupBy('topic_id', SPH_GROUPBY_ATTR, 'topic_last_post_time ' . (($sort_dir == 'a') ? 'ASC' : 'DESC')); + break; + } + } + else + { + switch ($sort_key) + { + case 'a': + $this->sphinx->SetSortMode(($sort_dir == 'a') ? SPH_SORT_ATTR_ASC : SPH_SORT_ATTR_DESC, 'poster_id'); + break; + + case 'f': + $this->sphinx->SetSortMode(($sort_dir == 'a') ? SPH_SORT_ATTR_ASC : SPH_SORT_ATTR_DESC, 'forum_id'); + break; + + case 'i': + + case 's': + $this->sphinx->SetSortMode(($sort_dir == 'a') ? SPH_SORT_ATTR_ASC : SPH_SORT_ATTR_DESC, 'post_subject'); + break; + + case 't': + + default: + $this->sphinx->SetSortMode(($sort_dir == 'a') ? SPH_SORT_ATTR_ASC : SPH_SORT_ATTR_DESC, 'post_time'); + break; + } + } + + // Most narrow filters first + if ($topic_id) + { + $this->sphinx->SetFilter('topic_id', array($topic_id)); + } + + $search_query_prefix = ''; + + switch ($fields) + { + case 'titleonly': + // Only search the title + if ($terms == 'all') + { + $search_query_prefix = '@title '; + } + // Weight for the title + $this->sphinx->SetFieldWeights(array("title" => 5, "data" => 1)); + // 1 is first_post, 0 is not first post + $this->sphinx->SetFilter('topic_first_post', array(1)); + break; + + case 'msgonly': + // Only search the body + if ($terms == 'all') + { + $search_query_prefix = '@data '; + } + // Weight for the body + $this->sphinx->SetFieldWeights(array("title" => 1, "data" => 5)); + break; + + case 'firstpost': + // More relative weight for the title, also search the body + $this->sphinx->SetFieldWeights(array("title" => 5, "data" => 1)); + // 1 is first_post, 0 is not first post + $this->sphinx->SetFilter('topic_first_post', array(1)); + break; + + default: + // More relative weight for the title, also search the body + $this->sphinx->SetFieldWeights(array("title" => 5, "data" => 1)); + break; + } + + if (sizeof($author_ary)) + { + $this->sphinx->SetFilter('poster_id', $author_ary); + } + + // As this is not simply possible at the moment, we limit the result to approved posts. + // This will make it impossible for moderators to search unapproved and softdeleted posts, + // but at least it will also cause the same for normal users. + $this->sphinx->SetFilter('post_visibility', array(ITEM_APPROVED)); + + if (sizeof($ex_fid_ary)) + { + // All forums that a user is allowed to access + $fid_ary = array_unique(array_intersect(array_keys($this->auth->acl_getf('f_read', true)), array_keys($this->auth->acl_getf('f_search', true)))); + // All forums that the user wants to and can search in + $search_forums = array_diff($fid_ary, $ex_fid_ary); + + if (sizeof($search_forums)) + { + $this->sphinx->SetFilter('forum_id', $search_forums); + } + } + + $this->sphinx->SetFilter('deleted', array(0)); + + $this->sphinx->SetLimits($start, (int) $per_page, SPHINX_MAX_MATCHES); + $result = $this->sphinx->Query($search_query_prefix . str_replace('"', '"', $this->search_query), $this->indexes); + + // Could be connection to localhost:9312 failed (errno=111, + // msg=Connection refused) during rotate, retry if so + $retries = SPHINX_CONNECT_RETRIES; + while (!$result && (strpos($this->sphinx->GetLastError(), "errno=111,") !== false) && $retries--) + { + usleep(SPHINX_CONNECT_WAIT_TIME); + $result = $this->sphinx->Query($search_query_prefix . str_replace('"', '"', $this->search_query), $this->indexes); + } + + if ($this->sphinx->GetLastError()) + { + add_log('critical', 'LOG_SPHINX_ERROR', $this->sphinx->GetLastError()); + if ($this->auth->acl_get('a_')) + { + trigger_error($this->user->lang('SPHINX_SEARCH_FAILED', $this->sphinx->GetLastError())); + } + else + { + trigger_error($this->user->lang('SPHINX_SEARCH_FAILED_LOG')); + } + } + + $result_count = $result['total_found']; + + if ($result_count && $start >= $result_count) + { + $start = floor(($result_count - 1) / $per_page) * $per_page; + + $this->sphinx->SetLimits((int) $start, (int) $per_page, SPHINX_MAX_MATCHES); + $result = $this->sphinx->Query($search_query_prefix . str_replace('"', '"', $this->search_query), $this->indexes); + + // Could be connection to localhost:9312 failed (errno=111, + // msg=Connection refused) during rotate, retry if so + $retries = SPHINX_CONNECT_RETRIES; + while (!$result && (strpos($this->sphinx->GetLastError(), "errno=111,") !== false) && $retries--) + { + usleep(SPHINX_CONNECT_WAIT_TIME); + $result = $this->sphinx->Query($search_query_prefix . str_replace('"', '"', $this->search_query), $this->indexes); + } + } + + $id_ary = array(); + if (isset($result['matches'])) + { + if ($type == 'posts') + { + $id_ary = array_keys($result['matches']); + } + else + { + foreach ($result['matches'] as $key => $value) + { + $id_ary[] = $value['attrs']['topic_id']; + } + } + } + else + { + return false; + } + + $id_ary = array_slice($id_ary, 0, (int) $per_page); + + return $result_count; + } + + /** + * Performs a search on an author's posts without caring about message contents. Depends on display specific params + * + * @param string $type contains either posts or topics depending on what should be searched for + * @param boolean $firstpost_only if true, only topic starting posts will be considered + * @param array $sort_by_sql contains SQL code for the ORDER BY part of a query + * @param string $sort_key is the key of $sort_by_sql for the selected sorting + * @param string $sort_dir is either a or d representing ASC and DESC + * @param string $sort_days specifies the maximum amount of days a post may be old + * @param array $ex_fid_ary specifies an array of forum ids which should not be searched + * @param string $post_visibility specifies which types of posts the user can view in which forums + * @param int $topic_id is set to 0 or a topic id, if it is not 0 then only posts in this topic should be searched + * @param array $author_ary an array of author ids + * @param string $author_name specifies the author match, when ANONYMOUS is also a search-match + * @param array &$id_ary passed by reference, to be filled with ids for the page specified by $start and $per_page, should be ordered + * @param int $start indicates the first index of the page + * @param int $per_page number of ids each page is supposed to contain + * @return boolean|int total number of results + */ + public function author_search($type, $firstpost_only, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $post_visibility, $topic_id, $author_ary, $author_name, &$id_ary, $start, $per_page) + { + $this->search_query = ''; + + $this->sphinx->SetMatchMode(SPH_MATCH_FULLSCAN); + $fields = ($firstpost_only) ? 'firstpost' : 'all'; + $terms = 'all'; + return $this->keyword_search($type, $fields, $terms, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $post_visibility, $topic_id, $author_ary, $author_name, $id_ary, $start, $per_page); + } + + /** + * Updates wordlist and wordmatch tables when a message is posted or changed + * + * @param string $mode Contains the post mode: edit, post, reply, quote + * @param int $post_id The id of the post which is modified/created + * @param string &$message New or updated post content + * @param string &$subject New or updated post subject + * @param int $poster_id Post author's user id + * @param int $forum_id The id of the forum in which the post is located + */ + public function index($mode, $post_id, &$message, &$subject, $poster_id, $forum_id) + { + if ($mode == 'edit') + { + $this->sphinx->UpdateAttributes($this->indexes, array('forum_id', 'poster_id'), array((int)$post_id => array((int)$forum_id, (int)$poster_id))); + } + else if ($mode != 'post' && $post_id) + { + // Update topic_last_post_time for full topic + $sql_array = array( + 'SELECT' => 'p1.post_id', + 'FROM' => array( + POSTS_TABLE => 'p1', + ), + 'LEFT_JOIN' => array(array( + 'FROM' => array( + POSTS_TABLE => 'p2' + ), + 'ON' => 'p1.topic_id = p2.topic_id', + )), + ); + + $sql = $this->db->sql_build_query('SELECT', $sql_array); + $result = $this->db->sql_query($sql); + + $post_updates = array(); + $post_time = time(); + while ($row = $this->db->sql_fetchrow($result)) + { + $post_updates[(int)$row['post_id']] = array($post_time); + } + $this->db->sql_freeresult($result); + + if (sizeof($post_updates)) + { + $this->sphinx->UpdateAttributes($this->indexes, array('topic_last_post_time'), $post_updates); + } + } + } + + /** + * Delete a post from the index after it was deleted + */ + public function index_remove($post_ids, $author_ids, $forum_ids) + { + $values = array(); + foreach ($post_ids as $post_id) + { + $values[$post_id] = array(1); + } + + $this->sphinx->UpdateAttributes($this->indexes, array('deleted'), $values); + } + + /** + * Nothing needs to be destroyed + */ + public function tidy($create = false) + { + set_config('search_last_gc', time(), true); + } + + /** + * Create sphinx table + * + * @return string|bool error string is returned incase of errors otherwise false + */ + public function create_index($acp_module, $u_action) + { + if (!$this->index_created()) + { + $table_data = array( + 'COLUMNS' => array( + 'counter_id' => array('UINT', 0), + 'max_doc_id' => array('UINT', 0), + ), + 'PRIMARY_KEY' => 'counter_id', + ); + $this->db_tools->sql_create_table(SPHINX_TABLE, $table_data); + + $sql = 'TRUNCATE TABLE ' . SPHINX_TABLE; + $this->db->sql_query($sql); + + $data = array( + 'counter_id' => '1', + 'max_doc_id' => '0', + ); + $sql = 'INSERT INTO ' . SPHINX_TABLE . ' ' . $this->db->sql_build_array('INSERT', $data); + $this->db->sql_query($sql); + } + + return false; + } + + /** + * Drop sphinx table + * + * @return string|bool error string is returned incase of errors otherwise false + */ + public function delete_index($acp_module, $u_action) + { + if (!$this->index_created()) + { + return false; + } + + $this->db_tools->sql_table_drop(SPHINX_TABLE); + + return false; + } + + /** + * Returns true if the sphinx table was created + * + * @return bool true if sphinx table was created + */ + public function index_created($allow_new_files = true) + { + $created = false; + + if ($this->db_tools->sql_table_exists(SPHINX_TABLE)) + { + $created = true; + } + + return $created; + } + + /** + * Returns an associative array containing information about the indexes + * + * @return string|bool Language string of error false otherwise + */ + public function index_stats() + { + if (empty($this->stats)) + { + $this->get_stats(); + } + + return array( + $this->user->lang['FULLTEXT_SPHINX_MAIN_POSTS'] => ($this->index_created()) ? $this->stats['main_posts'] : 0, + $this->user->lang['FULLTEXT_SPHINX_DELTA_POSTS'] => ($this->index_created()) ? $this->stats['total_posts'] - $this->stats['main_posts'] : 0, + $this->user->lang['FULLTEXT_MYSQL_TOTAL_POSTS'] => ($this->index_created()) ? $this->stats['total_posts'] : 0, + ); + } + + /** + * Collects stats that can be displayed on the index maintenance page + */ + protected function get_stats() + { + if ($this->index_created()) + { + $sql = 'SELECT COUNT(post_id) as total_posts + FROM ' . POSTS_TABLE; + $result = $this->db->sql_query($sql); + $this->stats['total_posts'] = (int) $this->db->sql_fetchfield('total_posts'); + $this->db->sql_freeresult($result); + + $sql = 'SELECT COUNT(p.post_id) as main_posts + FROM ' . POSTS_TABLE . ' p, ' . SPHINX_TABLE . ' m + WHERE p.post_id <= m.max_doc_id + AND m.counter_id = 1'; + $result = $this->db->sql_query($sql); + $this->stats['main_posts'] = (int) $this->db->sql_fetchfield('main_posts'); + $this->db->sql_freeresult($result); + } + } + + /** + * Returns a list of options for the ACP to display + * + * @return associative array containing template and config variables + */ + public function acp() + { + $config_vars = array( + 'fulltext_sphinx_data_path' => 'string', + 'fulltext_sphinx_host' => 'string', + 'fulltext_sphinx_port' => 'string', + 'fulltext_sphinx_indexer_mem_limit' => 'int', + ); + + $tpl = ' + ' . $this->user->lang['FULLTEXT_SPHINX_CONFIGURE']. ' +
+

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

' . $this->user->lang['FULLTEXT_SPHINX_HOST_EXPLAIN'] . '
+
+
+
+

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

' . $this->user->lang['FULLTEXT_SPHINX_INDEXER_MEM_LIMIT_EXPLAIN'] . '
+
' . $this->user->lang['MIB'] . '
+
+
+

' . $this->user->lang['FULLTEXT_SPHINX_CONFIG_FILE_EXPLAIN'] . '
+
' . (($this->config_generate()) ? '' : $this->config_file_data) . '
+
+ '; + + // These are fields required in the config table + return array( + 'tpl' => $tpl, + 'config' => $config_vars + ); + } +} diff --git a/phpBB/phpbb/search/index.htm b/phpBB/phpbb/search/index.htm new file mode 100644 index 0000000000..ee1f723a7d --- /dev/null +++ b/phpBB/phpbb/search/index.htm @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/phpBB/phpbb/search/sphinx/config.php b/phpBB/phpbb/search/sphinx/config.php new file mode 100644 index 0000000000..f1864f0c8c --- /dev/null +++ b/phpBB/phpbb/search/sphinx/config.php @@ -0,0 +1,288 @@ +read($config_data); + } + } + + /** + * Get a section object by its name + * + * @param string $name The name of the section that shall be returned + * @return phpbb_search_sphinx_config_section The section object or null if none was found + * + * @access public + */ + function get_section_by_name($name) + { + for ($i = 0, $size = sizeof($this->sections); $i < $size; $i++) + { + // Make sure this is really a section object and not a comment + if (($this->sections[$i] instanceof phpbb_search_sphinx_config_section) && $this->sections[$i]->get_name() == $name) + { + return $this->sections[$i]; + } + } + } + + /** + * Appends a new empty section to the end of the config + * + * @param string $name The name for the new section + * @return phpbb_search_sphinx_config_section The newly created section object + * + * @access public + */ + function add_section($name) + { + $this->sections[] = new phpbb_search_sphinx_config_section($name, ''); + return $this->sections[sizeof($this->sections) - 1]; + } + + /** + * Reads the config file data + * + * @param string $config_data The config file data + * + * @access private + */ + function read($config_data) + { + $this->sections = array(); + + $section = null; + $found_opening_bracket = false; + $in_value = false; + + foreach ($config_data as $i => $line) + { + // If the value of a variable continues to the next line because the line + // break was escaped then we don't trim leading space but treat it as a part of the value + if ($in_value) + { + $line = rtrim($line); + } + else + { + $line = trim($line); + } + + // If we're not inside a section look for one + if (!$section) + { + // Add empty lines and comments as comment objects to the section list + // that way they're not deleted when reassembling the file from the sections + if (!$line || $line[0] == '#') + { + $this->sections[] = new phpbb_search_sphinx_config_comment($config_file[$i]); + continue; + } + else + { + // Otherwise we scan the line reading the section name until we find + // an opening curly bracket or a comment + $section_name = ''; + $section_name_comment = ''; + $found_opening_bracket = false; + for ($j = 0, $length = strlen($line); $j < $length; $j++) + { + if ($line[$j] == '#') + { + $section_name_comment = substr($line, $j); + break; + } + + if ($found_opening_bracket) + { + continue; + } + + if ($line[$j] == '{') + { + $found_opening_bracket = true; + continue; + } + + $section_name .= $line[$j]; + } + + // And then we create the new section object + $section_name = trim($section_name); + $section = new phpbb_search_sphinx_config_section($section_name, $section_name_comment); + } + } + else + { + // If we're looking for variables inside a section + $skip_first = false; + + // If we're not in a value continuing over the line feed + if (!$in_value) + { + // Then add empty lines and comments as comment objects to the variable list + // of this section so they're not deleted on reassembly + if (!$line || $line[0] == '#') + { + $section->add_variable(new phpbb_search_sphinx_config_comment($config_file[$i])); + continue; + } + + // As long as we haven't yet actually found an opening bracket for this section + // we treat everything as comments so it's not deleted either + if (!$found_opening_bracket) + { + if ($line[0] == '{') + { + $skip_first = true; + $line = substr($line, 1); + $found_opening_bracket = true; + } + else + { + $section->add_variable(new phpbb_search_sphinx_config_comment($config_file[$i])); + continue; + } + } + } + + // If we did not find a comment in this line or still add to the previous + // line's value ... + if ($line || $in_value) + { + if (!$in_value) + { + $name = ''; + $value = ''; + $comment = ''; + $found_assignment = false; + } + $in_value = false; + $end_section = false; + + /* ... then we should prase this line char by char: + - first there's the variable name + - then an equal sign + - the variable value + - possibly a backslash before the linefeed in this case we need to continue + parsing the value in the next line + - a # indicating that the rest of the line is a comment + - a closing curly bracket indicating the end of this section*/ + for ($j = 0, $length = strlen($line); $j < $length; $j++) + { + if ($line[$j] == '#') + { + $comment = substr($line, $j); + break; + } + else if ($line[$j] == '}') + { + $comment = substr($line, $j + 1); + $end_section = true; + break; + } + else if (!$found_assignment) + { + if ($line[$j] == '=') + { + $found_assignment = true; + } + else + { + $name .= $line[$j]; + } + } + else + { + if ($line[$j] == '\\' && $j == $length - 1) + { + $value .= "\n"; + $in_value = true; + // Go to the next line and keep processing the value in there + continue 2; + } + $value .= $line[$j]; + } + } + + // If a name and an equal sign were found then we have append a + // new variable object to the section + if ($name && $found_assignment) + { + $section->add_variable(new phpbb_search_sphinx_config_variable(trim($name), trim($value), ($end_section) ? '' : $comment)); + continue; + } + + /* If we found a closing curly bracket this section has been completed + and we can append it to the section list and continue with looking for + the next section */ + if ($end_section) + { + $section->set_end_comment($comment); + $this->sections[] = $section; + $section = null; + continue; + } + } + + // If we did not find anything meaningful up to here, then just treat it + // as a comment + $comment = ($skip_first) ? "\t" . substr(ltrim($config_file[$i]), 1) : $config_file[$i]; + $section->add_variable(new phpbb_search_sphinx_config_comment($comment)); + } + } + + } + + /** + * Returns the config data + * + * @return string $data The config data that is generated + * + * @access public + */ + function get_data() + { + $data = ""; + foreach ($this->sections as $section) + { + $data .= $section->to_string(); + } + + return $data; + } +} diff --git a/phpBB/phpbb/search/sphinx/config_comment.php b/phpBB/phpbb/search/sphinx/config_comment.php new file mode 100644 index 0000000000..7f695dbf0c --- /dev/null +++ b/phpBB/phpbb/search/sphinx/config_comment.php @@ -0,0 +1,49 @@ +exact_string = $exact_string; + } + + /** + * Simply returns the comment as it was created + * + * @return string The exact string that was specified in the constructor + * + * @access public + */ + function to_string() + { + return $this->exact_string; + } +} diff --git a/phpBB/phpbb/search/sphinx/config_section.php b/phpBB/phpbb/search/sphinx/config_section.php new file mode 100644 index 0000000000..79c9c8563d --- /dev/null +++ b/phpBB/phpbb/search/sphinx/config_section.php @@ -0,0 +1,162 @@ +name = $name; + $this->comment = $comment; + $this->end_comment = ''; + } + + /** + * Add a variable object to the list of variables in this section + * + * @param phpbb_search_sphinx_config_variable $variable The variable object + * + * @access public + */ + function add_variable($variable) + { + $this->variables[] = $variable; + } + + /** + * Adds a comment after the closing bracket in the textual representation + * + * @param string $end_comment + * + * @access public + */ + function set_end_comment($end_comment) + { + $this->end_comment = $end_comment; + } + + /** + * Getter for the name of this section + * + * @return string Section's name + * + * @access public + */ + function get_name() + { + return $this->name; + } + + /** + * Get a variable object by its name + * + * @param string $name The name of the variable that shall be returned + * @return phpbb_search_sphinx_config_section The first variable object from this section with the + * given name or null if none was found + * + * @access public + */ + function get_variable_by_name($name) + { + for ($i = 0, $size = sizeof($this->variables); $i < $size; $i++) + { + // Make sure this is a variable object and not a comment + if (($this->variables[$i] instanceof phpbb_search_sphinx_config_variable) && $this->variables[$i]->get_name() == $name) + { + return $this->variables[$i]; + } + } + } + + /** + * Deletes all variables with the given name + * + * @param string $name The name of the variable objects that are supposed to be removed + * + * @access public + */ + function delete_variables_by_name($name) + { + for ($i = 0, $size = sizeof($this->variables); $i < $size; $i++) + { + // Make sure this is a variable object and not a comment + if (($this->variables[$i] instanceof phpbb_search_sphinx_config_variable) && $this->variables[$i]->get_name() == $name) + { + array_splice($this->variables, $i, 1); + $i--; + } + } + } + + /** + * Create a new variable object and append it to the variable list of this section + * + * @param string $name The name for the new variable + * @param string $value The value for the new variable + * @return phpbb_search_sphinx_config_variable Variable object that was created + * + * @access public + */ + function create_variable($name, $value) + { + $this->variables[] = new phpbb_search_sphinx_config_variable($name, $value, ''); + return $this->variables[sizeof($this->variables) - 1]; + } + + /** + * Turns this object into a string which can be written to a config file + * + * @return string Config data in textual form, parsable for sphinx + * + * @access public + */ + function to_string() + { + $content = $this->name . ' ' . $this->comment . "\n{\n"; + + // Make sure we don't get too many newlines after the opening bracket + while (trim($this->variables[0]->to_string()) == '') + { + array_shift($this->variables); + } + + foreach ($this->variables as $variable) + { + $content .= $variable->to_string(); + } + $content .= '}' . $this->end_comment . "\n"; + + return $content; + } +} diff --git a/phpBB/phpbb/search/sphinx/config_variable.php b/phpBB/phpbb/search/sphinx/config_variable.php new file mode 100644 index 0000000000..2c1d35a49c --- /dev/null +++ b/phpBB/phpbb/search/sphinx/config_variable.php @@ -0,0 +1,80 @@ +name = $name; + $this->value = $value; + $this->comment = $comment; + } + + /** + * Getter for the variable's name + * + * @return string The variable object's name + * + * @access public + */ + function get_name() + { + return $this->name; + } + + /** + * Allows changing the variable's value + * + * @param string $value New value for this variable + * + * @access public + */ + function set_value($value) + { + $this->value = $value; + } + + /** + * Turns this object into a string readable by sphinx + * + * @return string Config data in textual form + * + * @access public + */ + function to_string() + { + return "\t" . $this->name . ' = ' . str_replace("\n", " \\\n", $this->value) . ' ' . $this->comment . "\n"; + } +} diff --git a/phpBB/phpbb/session.php b/phpBB/phpbb/session.php new file mode 100644 index 0000000000..e0585b1523 --- /dev/null +++ b/phpBB/phpbb/session.php @@ -0,0 +1,1516 @@ +server('PHP_SELF')); + $args = explode('&', htmlspecialchars_decode($request->server('QUERY_STRING'))); + + // If we are unable to get the script name we use REQUEST_URI as a failover and note it within the page array for easier support... + if (!$script_name) + { + $script_name = htmlspecialchars_decode($request->server('REQUEST_URI')); + $script_name = (($pos = strpos($script_name, '?')) !== false) ? substr($script_name, 0, $pos) : $script_name; + $page_array['failover'] = 1; + } + + // Replace backslashes and doubled slashes (could happen on some proxy setups) + $script_name = str_replace(array('\\', '//'), '/', $script_name); + + // Now, remove the sid and let us get a clean query string... + $use_args = array(); + + // Since some browser do not encode correctly we need to do this with some "special" characters... + // " -> %22, ' => %27, < -> %3C, > -> %3E + $find = array('"', "'", '<', '>'); + $replace = array('%22', '%27', '%3C', '%3E'); + + foreach ($args as $key => $argument) + { + if (strpos($argument, 'sid=') === 0) + { + continue; + } + + $use_args[] = str_replace($find, $replace, $argument); + } + unset($args); + + // The following examples given are for an request uri of {path to the phpbb directory}/adm/index.php?i=10&b=2 + + // The current query string + $query_string = trim(implode('&', $use_args)); + + // basenamed page name (for example: index.php) + $page_name = (substr($script_name, -1, 1) == '/') ? '' : basename($script_name); + $page_name = urlencode(htmlspecialchars($page_name)); + + // current directory within the phpBB root (for example: adm) + $root_dirs = explode('/', str_replace('\\', '/', phpbb_realpath($root_path))); + $page_dirs = explode('/', str_replace('\\', '/', phpbb_realpath('./'))); + $intersection = array_intersect_assoc($root_dirs, $page_dirs); + + $root_dirs = array_diff_assoc($root_dirs, $intersection); + $page_dirs = array_diff_assoc($page_dirs, $intersection); + + $page_dir = str_repeat('../', sizeof($root_dirs)) . implode('/', $page_dirs); + + if ($page_dir && substr($page_dir, -1, 1) == '/') + { + $page_dir = substr($page_dir, 0, -1); + } + + // Current page from phpBB root (for example: adm/index.php?i=10&b=2) + $page = (($page_dir) ? $page_dir . '/' : '') . $page_name . (($query_string) ? "?$query_string" : ''); + + // The script path from the webroot to the current directory (for example: /phpBB3/adm/) : always prefixed with / and ends in / + $script_path = trim(str_replace('\\', '/', dirname($script_name))); + + // The script path from the webroot to the phpBB root (for example: /phpBB3/) + $script_dirs = explode('/', $script_path); + array_splice($script_dirs, -sizeof($page_dirs)); + $root_script_path = implode('/', $script_dirs) . (sizeof($root_dirs) ? '/' . implode('/', $root_dirs) : ''); + + // We are on the base level (phpBB root == webroot), lets adjust the variables a bit... + if (!$root_script_path) + { + $root_script_path = ($page_dir) ? str_replace($page_dir, '', $script_path) : $script_path; + } + + $script_path .= (substr($script_path, -1, 1) == '/') ? '' : '/'; + $root_script_path .= (substr($root_script_path, -1, 1) == '/') ? '' : '/'; + + $page_array += array( + 'page_name' => $page_name, + 'page_dir' => $page_dir, + + 'query_string' => $query_string, + 'script_path' => str_replace(' ', '%20', htmlspecialchars($script_path)), + 'root_script_path' => str_replace(' ', '%20', htmlspecialchars($root_script_path)), + + 'page' => $page, + 'forum' => request_var('f', 0), + ); + + return $page_array; + } + + /** + * Get valid hostname/port. HTTP_HOST is used, SERVER_NAME if HTTP_HOST not present. + */ + function extract_current_hostname() + { + global $config, $request; + + // Get hostname + $host = htmlspecialchars_decode($request->header('Host', $request->server('SERVER_NAME'))); + + // Should be a string and lowered + $host = (string) strtolower($host); + + // If host is equal the cookie domain or the server name (if config is set), then we assume it is valid + if ((isset($config['cookie_domain']) && $host === $config['cookie_domain']) || (isset($config['server_name']) && $host === $config['server_name'])) + { + return $host; + } + + // Is the host actually a IP? If so, we use the IP... (IPv4) + if (long2ip(ip2long($host)) === $host) + { + return $host; + } + + // Now return the hostname (this also removes any port definition). The http:// is prepended to construct a valid URL, hosts never have a scheme assigned + $host = @parse_url('http://' . $host); + $host = (!empty($host['host'])) ? $host['host'] : ''; + + // Remove any portions not removed by parse_url (#) + $host = str_replace('#', '', $host); + + // If, by any means, the host is now empty, we will use a "best approach" way to guess one + if (empty($host)) + { + if (!empty($config['server_name'])) + { + $host = $config['server_name']; + } + else if (!empty($config['cookie_domain'])) + { + $host = (strpos($config['cookie_domain'], '.') === 0) ? substr($config['cookie_domain'], 1) : $config['cookie_domain']; + } + else + { + // Set to OS hostname or localhost + $host = (function_exists('php_uname')) ? php_uname('n') : 'localhost'; + } + } + + // It may be still no valid host, but for sure only a hostname (we may further expand on the cookie domain... if set) + return $host; + } + + /** + * Start session management + * + * This is where all session activity begins. We gather various pieces of + * information from the client and server. We test to see if a session already + * exists. If it does, fine and dandy. If it doesn't we'll go on to create a + * new one ... pretty logical heh? We also examine the system load (if we're + * running on a system which makes such information readily available) and + * halt if it's above an admin definable limit. + * + * @param bool $update_session_page if true the session page gets updated. + * This can be set to circumvent certain scripts to update the users last visited page. + */ + function session_begin($update_session_page = true) + { + global $phpEx, $SID, $_SID, $_EXTRA_URL, $db, $config, $phpbb_root_path; + global $request, $phpbb_container; + + // Give us some basic information + $this->time_now = time(); + $this->cookie_data = array('u' => 0, 'k' => ''); + $this->update_session_page = $update_session_page; + $this->browser = $request->header('User-Agent'); + $this->referer = $request->header('Referer'); + $this->forwarded_for = $request->header('X-Forwarded-For'); + + $this->host = $this->extract_current_hostname(); + $this->page = $this->extract_current_page($phpbb_root_path); + + // if the forwarded for header shall be checked we have to validate its contents + if ($config['forwarded_for_check']) + { + $this->forwarded_for = preg_replace('# {2,}#', ' ', str_replace(',', ' ', $this->forwarded_for)); + + // split the list of IPs + $ips = explode(' ', $this->forwarded_for); + foreach ($ips as $ip) + { + // check IPv4 first, the IPv6 is hopefully only going to be used very seldomly + if (!empty($ip) && !preg_match(get_preg_expression('ipv4'), $ip) && !preg_match(get_preg_expression('ipv6'), $ip)) + { + // contains invalid data, don't use the forwarded for header + $this->forwarded_for = ''; + break; + } + } + } + else + { + $this->forwarded_for = ''; + } + + if ($request->is_set($config['cookie_name'] . '_sid', phpbb_request_interface::COOKIE) || $request->is_set($config['cookie_name'] . '_u', phpbb_request_interface::COOKIE)) + { + $this->cookie_data['u'] = request_var($config['cookie_name'] . '_u', 0, false, true); + $this->cookie_data['k'] = request_var($config['cookie_name'] . '_k', '', false, true); + $this->session_id = request_var($config['cookie_name'] . '_sid', '', false, true); + + $SID = (defined('NEED_SID')) ? '?sid=' . $this->session_id : '?sid='; + $_SID = (defined('NEED_SID')) ? $this->session_id : ''; + + if (empty($this->session_id)) + { + $this->session_id = $_SID = request_var('sid', ''); + $SID = '?sid=' . $this->session_id; + $this->cookie_data = array('u' => 0, 'k' => ''); + } + } + else + { + $this->session_id = $_SID = request_var('sid', ''); + $SID = '?sid=' . $this->session_id; + } + + $_EXTRA_URL = array(); + + // Why no forwarded_for et al? Well, too easily spoofed. With the results of my recent requests + // it's pretty clear that in the majority of cases you'll at least be left with a proxy/cache ip. + $this->ip = htmlspecialchars_decode($request->server('REMOTE_ADDR')); + $this->ip = preg_replace('# {2,}#', ' ', str_replace(',', ' ', $this->ip)); + + // split the list of IPs + $ips = explode(' ', trim($this->ip)); + + // Default IP if REMOTE_ADDR is invalid + $this->ip = '127.0.0.1'; + + foreach ($ips as $ip) + { + if (function_exists('phpbb_ip_normalise')) + { + // Normalise IP address + $ip = phpbb_ip_normalise($ip); + + if (empty($ip)) + { + // IP address is invalid. + break; + } + + // IP address is valid. + $this->ip = $ip; + + // Skip legacy code. + continue; + } + + if (preg_match(get_preg_expression('ipv4'), $ip)) + { + $this->ip = $ip; + } + else if (preg_match(get_preg_expression('ipv6'), $ip)) + { + // Quick check for IPv4-mapped address in IPv6 + if (stripos($ip, '::ffff:') === 0) + { + $ipv4 = substr($ip, 7); + + if (preg_match(get_preg_expression('ipv4'), $ipv4)) + { + $ip = $ipv4; + } + } + + $this->ip = $ip; + } + else + { + // We want to use the last valid address in the chain + // Leave foreach loop when address is invalid + break; + } + } + + $this->load = false; + + // Load limit check (if applicable) + if ($config['limit_load'] || $config['limit_search_load']) + { + if ((function_exists('sys_getloadavg') && $load = sys_getloadavg()) || ($load = explode(' ', @file_get_contents('/proc/loadavg')))) + { + $this->load = array_slice($load, 0, 1); + $this->load = floatval($this->load[0]); + } + else + { + set_config('limit_load', '0'); + set_config('limit_search_load', '0'); + } + } + + // if no session id is set, redirect to index.php + $session_id = $request->variable('sid', ''); + if (defined('NEED_SID') && (empty($session_id) || $this->session_id !== $session_id)) + { + send_status_line(401, 'Unauthorized'); + redirect(append_sid("{$phpbb_root_path}index.$phpEx")); + } + + // if session id is set + if (!empty($this->session_id)) + { + $sql = 'SELECT u.*, s.* + FROM ' . SESSIONS_TABLE . ' s, ' . USERS_TABLE . " u + WHERE s.session_id = '" . $db->sql_escape($this->session_id) . "' + AND u.user_id = s.session_user_id"; + $result = $db->sql_query($sql); + $this->data = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + // Did the session exist in the DB? + if (isset($this->data['user_id'])) + { + // Validate IP length according to admin ... enforces an IP + // check on bots if admin requires this +// $quadcheck = ($config['ip_check_bot'] && $this->data['user_type'] & USER_BOT) ? 4 : $config['ip_check']; + + if (strpos($this->ip, ':') !== false && strpos($this->data['session_ip'], ':') !== false) + { + $s_ip = short_ipv6($this->data['session_ip'], $config['ip_check']); + $u_ip = short_ipv6($this->ip, $config['ip_check']); + } + else + { + $s_ip = implode('.', array_slice(explode('.', $this->data['session_ip']), 0, $config['ip_check'])); + $u_ip = implode('.', array_slice(explode('.', $this->ip), 0, $config['ip_check'])); + } + + $s_browser = ($config['browser_check']) ? trim(strtolower(substr($this->data['session_browser'], 0, 149))) : ''; + $u_browser = ($config['browser_check']) ? trim(strtolower(substr($this->browser, 0, 149))) : ''; + + $s_forwarded_for = ($config['forwarded_for_check']) ? substr($this->data['session_forwarded_for'], 0, 254) : ''; + $u_forwarded_for = ($config['forwarded_for_check']) ? substr($this->forwarded_for, 0, 254) : ''; + + // referer checks + // The @ before $config['referer_validation'] suppresses notices present while running the updater + $check_referer_path = (@$config['referer_validation'] == REFERER_VALIDATE_PATH); + $referer_valid = true; + + // we assume HEAD and TRACE to be foul play and thus only whitelist GET + if (@$config['referer_validation'] && strtolower($request->server('REQUEST_METHOD')) !== 'get') + { + $referer_valid = $this->validate_referer($check_referer_path); + } + + if ($u_ip === $s_ip && $s_browser === $u_browser && $s_forwarded_for === $u_forwarded_for && $referer_valid) + { + $session_expired = false; + + // Check whether the session is still valid if we have one + $method = basename(trim($config['auth_method'])); + + $provider = $phpbb_container->get('auth.provider.' . $method); + + if (!($provider instanceof phpbb_auth_provider_interface)) + { + throw new \RuntimeException($provider . ' must implement phpbb_auth_provider_interface'); + } + + $ret = $provider->validate_session($this->data); + if ($ret !== null && !$ret) + { + $session_expired = true; + } + + if (!$session_expired) + { + // Check the session length timeframe if autologin is not enabled. + // Else check the autologin length... and also removing those having autologin enabled but no longer allowed board-wide. + if (!$this->data['session_autologin']) + { + if ($this->data['session_time'] < $this->time_now - ($config['session_length'] + 60)) + { + $session_expired = true; + } + } + else if (!$config['allow_autologin'] || ($config['max_autologin_time'] && $this->data['session_time'] < $this->time_now - (86400 * (int) $config['max_autologin_time']) + 60)) + { + $session_expired = true; + } + } + + if (!$session_expired) + { + // Only update session DB a minute or so after last update or if page changes + if ($this->time_now - $this->data['session_time'] > 60 || ($this->update_session_page && $this->data['session_page'] != $this->page['page'])) + { + $sql_ary = array('session_time' => $this->time_now); + + if ($this->update_session_page) + { + $sql_ary['session_page'] = substr($this->page['page'], 0, 199); + $sql_ary['session_forum_id'] = $this->page['forum']; + } + + $db->sql_return_on_error(true); + + $this->update_session($sql_ary); + + $db->sql_return_on_error(false); + + // If the database is not yet updated, there will be an error due to the session_forum_id + // @todo REMOVE for 3.0.2 + if ($result === false) + { + unset($sql_ary['session_forum_id']); + + $this->update_session($sql_ary); + } + + if ($this->data['user_id'] != ANONYMOUS && !empty($config['new_member_post_limit']) && $this->data['user_new'] && $config['new_member_post_limit'] <= $this->data['user_posts']) + { + $this->leave_newly_registered(); + } + } + + $this->data['is_registered'] = ($this->data['user_id'] != ANONYMOUS && ($this->data['user_type'] == USER_NORMAL || $this->data['user_type'] == USER_FOUNDER)) ? true : false; + $this->data['is_bot'] = (!$this->data['is_registered'] && $this->data['user_id'] != ANONYMOUS) ? true : false; + $this->data['user_lang'] = basename($this->data['user_lang']); + + return true; + } + } + else + { + // Added logging temporarly to help debug bugs... + if (defined('DEBUG') && $this->data['user_id'] != ANONYMOUS) + { + if ($referer_valid) + { + add_log('critical', 'LOG_IP_BROWSER_FORWARDED_CHECK', $u_ip, $s_ip, $u_browser, $s_browser, htmlspecialchars($u_forwarded_for), htmlspecialchars($s_forwarded_for)); + } + else + { + add_log('critical', 'LOG_REFERER_INVALID', $this->referer); + } + } + } + } + } + + // If we reach here then no (valid) session exists. So we'll create a new one + return $this->session_create(); + } + + /** + * Create a new session + * + * If upon trying to start a session we discover there is nothing existing we + * jump here. Additionally this method is called directly during login to regenerate + * the session for the specific user. In this method we carry out a number of tasks; + * garbage collection, (search)bot checking, banned user comparison. Basically + * though this method will result in a new session for a specific user. + */ + function session_create($user_id = false, $set_admin = false, $persist_login = false, $viewonline = true) + { + global $SID, $_SID, $db, $config, $cache, $phpbb_root_path, $phpEx, $phpbb_container; + + $this->data = array(); + + /* Garbage collection ... remove old sessions updating user information + // if necessary. It means (potentially) 11 queries but only infrequently + if ($this->time_now > $config['session_last_gc'] + $config['session_gc']) + { + $this->session_gc(); + }*/ + + // Do we allow autologin on this board? No? Then override anything + // that may be requested here + if (!$config['allow_autologin']) + { + $this->cookie_data['k'] = $persist_login = false; + } + + /** + * Here we do a bot check, oh er saucy! No, not that kind of bot + * check. We loop through the list of bots defined by the admin and + * see if we have any useragent and/or IP matches. If we do, this is a + * bot, act accordingly + */ + $bot = false; + $active_bots = $cache->obtain_bots(); + + foreach ($active_bots as $row) + { + if ($row['bot_agent'] && preg_match('#' . str_replace('\*', '.*?', preg_quote($row['bot_agent'], '#')) . '#i', $this->browser)) + { + $bot = $row['user_id']; + } + + // If ip is supplied, we will make sure the ip is matching too... + if ($row['bot_ip'] && ($bot || !$row['bot_agent'])) + { + // Set bot to false, then we only have to set it to true if it is matching + $bot = false; + + foreach (explode(',', $row['bot_ip']) as $bot_ip) + { + $bot_ip = trim($bot_ip); + + if (!$bot_ip) + { + continue; + } + + if (strpos($this->ip, $bot_ip) === 0) + { + $bot = (int) $row['user_id']; + break; + } + } + } + + if ($bot) + { + break; + } + } + + $method = basename(trim($config['auth_method'])); + + $provider = $phpbb_container->get('auth.provider.' . $method); + $this->data = $provider->autologin(); + + if (sizeof($this->data)) + { + $this->cookie_data['k'] = ''; + $this->cookie_data['u'] = $this->data['user_id']; + } + + // If we're presented with an autologin key we'll join against it. + // Else if we've been passed a user_id we'll grab data based on that + if (isset($this->cookie_data['k']) && $this->cookie_data['k'] && $this->cookie_data['u'] && !sizeof($this->data)) + { + $sql = 'SELECT u.* + FROM ' . USERS_TABLE . ' u, ' . SESSIONS_KEYS_TABLE . ' k + WHERE u.user_id = ' . (int) $this->cookie_data['u'] . ' + AND u.user_type IN (' . USER_NORMAL . ', ' . USER_FOUNDER . ") + AND k.user_id = u.user_id + AND k.key_id = '" . $db->sql_escape(md5($this->cookie_data['k'])) . "'"; + $result = $db->sql_query($sql); + $this->data = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + $bot = false; + } + else if ($user_id !== false && !sizeof($this->data)) + { + $this->cookie_data['k'] = ''; + $this->cookie_data['u'] = $user_id; + + $sql = 'SELECT * + FROM ' . USERS_TABLE . ' + WHERE user_id = ' . (int) $this->cookie_data['u'] . ' + AND user_type IN (' . USER_NORMAL . ', ' . USER_FOUNDER . ')'; + $result = $db->sql_query($sql); + $this->data = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + $bot = false; + } + + // Bot user, if they have a SID in the Request URI we need to get rid of it + // otherwise they'll index this page with the SID, duplicate content oh my! + if ($bot && isset($_GET['sid'])) + { + send_status_line(301, 'Moved Permanently'); + redirect(build_url(array('sid'))); + } + + // If no data was returned one or more of the following occurred: + // Key didn't match one in the DB + // User does not exist + // User is inactive + // User is bot + if (!sizeof($this->data) || !is_array($this->data)) + { + $this->cookie_data['k'] = ''; + $this->cookie_data['u'] = ($bot) ? $bot : ANONYMOUS; + + if (!$bot) + { + $sql = 'SELECT * + FROM ' . USERS_TABLE . ' + WHERE user_id = ' . (int) $this->cookie_data['u']; + } + else + { + // We give bots always the same session if it is not yet expired. + $sql = 'SELECT u.*, s.* + FROM ' . USERS_TABLE . ' u + LEFT JOIN ' . SESSIONS_TABLE . ' s ON (s.session_user_id = u.user_id) + WHERE u.user_id = ' . (int) $bot; + } + + $result = $db->sql_query($sql); + $this->data = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + } + + if ($this->data['user_id'] != ANONYMOUS && !$bot) + { + $this->data['session_last_visit'] = (isset($this->data['session_time']) && $this->data['session_time']) ? $this->data['session_time'] : (($this->data['user_lastvisit']) ? $this->data['user_lastvisit'] : time()); + } + else + { + $this->data['session_last_visit'] = $this->time_now; + } + + // Force user id to be integer... + $this->data['user_id'] = (int) $this->data['user_id']; + + // At this stage we should have a filled data array, defined cookie u and k data. + // data array should contain recent session info if we're a real user and a recent + // session exists in which case session_id will also be set + + // Is user banned? Are they excluded? Won't return on ban, exists within method + if ($this->data['user_type'] != USER_FOUNDER) + { + if (!$config['forwarded_for_check']) + { + $this->check_ban($this->data['user_id'], $this->ip); + } + else + { + $ips = explode(' ', $this->forwarded_for); + $ips[] = $this->ip; + $this->check_ban($this->data['user_id'], $ips); + } + } + + $this->data['is_registered'] = (!$bot && $this->data['user_id'] != ANONYMOUS && ($this->data['user_type'] == USER_NORMAL || $this->data['user_type'] == USER_FOUNDER)) ? true : false; + $this->data['is_bot'] = ($bot) ? true : false; + + // If our friend is a bot, we re-assign a previously assigned session + if ($this->data['is_bot'] && $bot == $this->data['user_id'] && $this->data['session_id']) + { + // Only assign the current session if the ip, browser and forwarded_for match... + if (strpos($this->ip, ':') !== false && strpos($this->data['session_ip'], ':') !== false) + { + $s_ip = short_ipv6($this->data['session_ip'], $config['ip_check']); + $u_ip = short_ipv6($this->ip, $config['ip_check']); + } + else + { + $s_ip = implode('.', array_slice(explode('.', $this->data['session_ip']), 0, $config['ip_check'])); + $u_ip = implode('.', array_slice(explode('.', $this->ip), 0, $config['ip_check'])); + } + + $s_browser = ($config['browser_check']) ? trim(strtolower(substr($this->data['session_browser'], 0, 149))) : ''; + $u_browser = ($config['browser_check']) ? trim(strtolower(substr($this->browser, 0, 149))) : ''; + + $s_forwarded_for = ($config['forwarded_for_check']) ? substr($this->data['session_forwarded_for'], 0, 254) : ''; + $u_forwarded_for = ($config['forwarded_for_check']) ? substr($this->forwarded_for, 0, 254) : ''; + + if ($u_ip === $s_ip && $s_browser === $u_browser && $s_forwarded_for === $u_forwarded_for) + { + $this->session_id = $this->data['session_id']; + + // Only update session DB a minute or so after last update or if page changes + if ($this->time_now - $this->data['session_time'] > 60 || ($this->update_session_page && $this->data['session_page'] != $this->page['page'])) + { + $this->data['session_time'] = $this->data['session_last_visit'] = $this->time_now; + + $sql_ary = array('session_time' => $this->time_now, 'session_last_visit' => $this->time_now, 'session_admin' => 0); + + if ($this->update_session_page) + { + $sql_ary['session_page'] = substr($this->page['page'], 0, 199); + $sql_ary['session_forum_id'] = $this->page['forum']; + } + + $this->update_session($sql_ary); + + // Update the last visit time + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_lastvisit = ' . (int) $this->data['session_time'] . ' + WHERE user_id = ' . (int) $this->data['user_id']; + $db->sql_query($sql); + } + + $SID = '?sid='; + $_SID = ''; + return true; + } + else + { + // If the ip and browser does not match make sure we only have one bot assigned to one session + $db->sql_query('DELETE FROM ' . SESSIONS_TABLE . ' WHERE session_user_id = ' . $this->data['user_id']); + } + } + + $session_autologin = (($this->cookie_data['k'] || $persist_login) && $this->data['is_registered']) ? true : false; + $set_admin = ($set_admin && $this->data['is_registered']) ? true : false; + + // Create or update the session + $sql_ary = array( + 'session_user_id' => (int) $this->data['user_id'], + 'session_start' => (int) $this->time_now, + 'session_last_visit' => (int) $this->data['session_last_visit'], + 'session_time' => (int) $this->time_now, + 'session_browser' => (string) trim(substr($this->browser, 0, 149)), + 'session_forwarded_for' => (string) $this->forwarded_for, + 'session_ip' => (string) $this->ip, + 'session_autologin' => ($session_autologin) ? 1 : 0, + 'session_admin' => ($set_admin) ? 1 : 0, + 'session_viewonline' => ($viewonline) ? 1 : 0, + ); + + if ($this->update_session_page) + { + $sql_ary['session_page'] = (string) substr($this->page['page'], 0, 199); + $sql_ary['session_forum_id'] = $this->page['forum']; + } + + $db->sql_return_on_error(true); + + $sql = 'DELETE + FROM ' . SESSIONS_TABLE . ' + WHERE session_id = \'' . $db->sql_escape($this->session_id) . '\' + AND session_user_id = ' . ANONYMOUS; + + if (!defined('IN_ERROR_HANDLER') && (!$this->session_id || !$db->sql_query($sql) || !$db->sql_affectedrows())) + { + // Limit new sessions in 1 minute period (if required) + if (empty($this->data['session_time']) && $config['active_sessions']) + { +// $db->sql_return_on_error(false); + + $sql = 'SELECT COUNT(session_id) AS sessions + FROM ' . SESSIONS_TABLE . ' + WHERE session_time >= ' . ($this->time_now - 60); + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if ((int) $row['sessions'] > (int) $config['active_sessions']) + { + send_status_line(503, 'Service Unavailable'); + trigger_error('BOARD_UNAVAILABLE'); + } + } + } + + // Since we re-create the session id here, the inserted row must be unique. Therefore, we display potential errors. + // Commented out because it will not allow forums to update correctly +// $db->sql_return_on_error(false); + + // Something quite important: session_page always holds the *last* page visited, except for the *first* visit. + // We are not able to simply have an empty session_page btw, therefore we need to tell phpBB how to detect this special case. + // If the session id is empty, we have a completely new one and will set an "identifier" here. This identifier is able to be checked later. + if (empty($this->data['session_id'])) + { + // This is a temporary variable, only set for the very first visit + $this->data['session_created'] = true; + } + + $this->session_id = $this->data['session_id'] = md5(unique_id()); + + $sql_ary['session_id'] = (string) $this->session_id; + $sql_ary['session_page'] = (string) substr($this->page['page'], 0, 199); + $sql_ary['session_forum_id'] = $this->page['forum']; + + $sql = 'INSERT INTO ' . SESSIONS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary); + $db->sql_query($sql); + + $db->sql_return_on_error(false); + + // Regenerate autologin/persistent login key + if ($session_autologin) + { + $this->set_login_key(); + } + + // refresh data + $SID = '?sid=' . $this->session_id; + $_SID = $this->session_id; + $this->data = array_merge($this->data, $sql_ary); + + if (!$bot) + { + $cookie_expire = $this->time_now + (($config['max_autologin_time']) ? 86400 * (int) $config['max_autologin_time'] : 31536000); + + $this->set_cookie('u', $this->cookie_data['u'], $cookie_expire); + $this->set_cookie('k', $this->cookie_data['k'], $cookie_expire); + $this->set_cookie('sid', $this->session_id, $cookie_expire); + + unset($cookie_expire); + + $sql = 'SELECT COUNT(session_id) AS sessions + FROM ' . SESSIONS_TABLE . ' + WHERE session_user_id = ' . (int) $this->data['user_id'] . ' + AND session_time >= ' . (int) ($this->time_now - (max($config['session_length'], $config['form_token_lifetime']))); + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if ((int) $row['sessions'] <= 1 || empty($this->data['user_form_salt'])) + { + $this->data['user_form_salt'] = unique_id(); + // Update the form key + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_form_salt = \'' . $db->sql_escape($this->data['user_form_salt']) . '\' + WHERE user_id = ' . (int) $this->data['user_id']; + $db->sql_query($sql); + } + } + else + { + $this->data['session_time'] = $this->data['session_last_visit'] = $this->time_now; + + // Update the last visit time + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_lastvisit = ' . (int) $this->data['session_time'] . ' + WHERE user_id = ' . (int) $this->data['user_id']; + $db->sql_query($sql); + + $SID = '?sid='; + $_SID = ''; + } + + return true; + } + + /** + * Kills a session + * + * This method does what it says on the tin. It will delete a pre-existing session. + * It resets cookie information (destroying any autologin key within that cookie data) + * and update the users information from the relevant session data. It will then + * grab guest user information. + */ + function session_kill($new_session = true) + { + global $SID, $_SID, $db, $config, $phpbb_root_path, $phpEx, $phpbb_container; + + $sql = 'DELETE FROM ' . SESSIONS_TABLE . " + WHERE session_id = '" . $db->sql_escape($this->session_id) . "' + AND session_user_id = " . (int) $this->data['user_id']; + $db->sql_query($sql); + + // Allow connecting logout with external auth method logout + $method = basename(trim($config['auth_method'])); + + $provider = $phpbb_container->get('auth.provider.' . $method); + $provider->logout($this->data, $new_session); + + if ($this->data['user_id'] != ANONYMOUS) + { + // Delete existing session, update last visit info first! + if (!isset($this->data['session_time'])) + { + $this->data['session_time'] = time(); + } + + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_lastvisit = ' . (int) $this->data['session_time'] . ' + WHERE user_id = ' . (int) $this->data['user_id']; + $db->sql_query($sql); + + if ($this->cookie_data['k']) + { + $sql = 'DELETE FROM ' . SESSIONS_KEYS_TABLE . ' + WHERE user_id = ' . (int) $this->data['user_id'] . " + AND key_id = '" . $db->sql_escape(md5($this->cookie_data['k'])) . "'"; + $db->sql_query($sql); + } + + // Reset the data array + $this->data = array(); + + $sql = 'SELECT * + FROM ' . USERS_TABLE . ' + WHERE user_id = ' . ANONYMOUS; + $result = $db->sql_query($sql); + $this->data = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + } + + $cookie_expire = $this->time_now - 31536000; + $this->set_cookie('u', '', $cookie_expire); + $this->set_cookie('k', '', $cookie_expire); + $this->set_cookie('sid', '', $cookie_expire); + unset($cookie_expire); + + $SID = '?sid='; + $this->session_id = $_SID = ''; + + // To make sure a valid session is created we create one for the anonymous user + if ($new_session) + { + $this->session_create(ANONYMOUS); + } + + return true; + } + + /** + * Session garbage collection + * + * This looks a lot more complex than it really is. Effectively we are + * deleting any sessions older than an admin definable limit. Due to the + * way in which we maintain session data we have to ensure we update user + * data before those sessions are destroyed. In addition this method + * removes autologin key information that is older than an admin defined + * limit. + */ + function session_gc() + { + global $db, $config, $phpbb_root_path, $phpEx; + + $batch_size = 10; + + if (!$this->time_now) + { + $this->time_now = time(); + } + + // Firstly, delete guest sessions + $sql = 'DELETE FROM ' . SESSIONS_TABLE . ' + WHERE session_user_id = ' . ANONYMOUS . ' + AND session_time < ' . (int) ($this->time_now - $config['session_length']); + $db->sql_query($sql); + + // Get expired sessions, only most recent for each user + $sql = 'SELECT session_user_id, session_page, MAX(session_time) AS recent_time + FROM ' . SESSIONS_TABLE . ' + WHERE session_time < ' . ($this->time_now - $config['session_length']) . ' + GROUP BY session_user_id, session_page'; + $result = $db->sql_query_limit($sql, $batch_size); + + $del_user_id = array(); + $del_sessions = 0; + + while ($row = $db->sql_fetchrow($result)) + { + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_lastvisit = ' . (int) $row['recent_time'] . ", user_lastpage = '" . $db->sql_escape($row['session_page']) . "' + WHERE user_id = " . (int) $row['session_user_id']; + $db->sql_query($sql); + + $del_user_id[] = (int) $row['session_user_id']; + $del_sessions++; + } + $db->sql_freeresult($result); + + if (sizeof($del_user_id)) + { + // Delete expired sessions + $sql = 'DELETE FROM ' . SESSIONS_TABLE . ' + WHERE ' . $db->sql_in_set('session_user_id', $del_user_id) . ' + AND session_time < ' . ($this->time_now - $config['session_length']); + $db->sql_query($sql); + } + + if ($del_sessions < $batch_size) + { + // Less than 10 users, update gc timer ... else we want gc + // called again to delete other sessions + set_config('session_last_gc', $this->time_now, true); + + if ($config['max_autologin_time']) + { + $sql = 'DELETE FROM ' . SESSIONS_KEYS_TABLE . ' + WHERE last_login < ' . (time() - (86400 * (int) $config['max_autologin_time'])); + $db->sql_query($sql); + } + + // only called from CRON; should be a safe workaround until the infrastructure gets going + if (!class_exists('phpbb_captcha_factory', false)) + { + include($phpbb_root_path . "includes/captcha/captcha_factory." . $phpEx); + } + phpbb_captcha_factory::garbage_collect($config['captcha_plugin']); + + $sql = 'DELETE FROM ' . LOGIN_ATTEMPT_TABLE . ' + WHERE attempt_time < ' . (time() - (int) $config['ip_login_limit_time']); + $db->sql_query($sql); + } + + return; + } + + /** + * Sets a cookie + * + * Sets a cookie of the given name with the specified data for the given length of time. If no time is specified, a session cookie will be set. + * + * @param string $name Name of the cookie, will be automatically prefixed with the phpBB cookie name. track becomes [cookie_name]_track then. + * @param string $cookiedata The data to hold within the cookie + * @param int $cookietime The expiration time as UNIX timestamp. If 0 is provided, a session cookie is set. + */ + function set_cookie($name, $cookiedata, $cookietime) + { + global $config; + + $name_data = rawurlencode($config['cookie_name'] . '_' . $name) . '=' . rawurlencode($cookiedata); + $expire = gmdate('D, d-M-Y H:i:s \\G\\M\\T', $cookietime); + $domain = (!$config['cookie_domain'] || $config['cookie_domain'] == 'localhost' || $config['cookie_domain'] == '127.0.0.1') ? '' : '; domain=' . $config['cookie_domain']; + + header('Set-Cookie: ' . $name_data . (($cookietime) ? '; expires=' . $expire : '') . '; path=' . $config['cookie_path'] . $domain . ((!$config['cookie_secure']) ? '' : '; secure') . '; HttpOnly', false); + } + + /** + * Check for banned user + * + * Checks whether the supplied user is banned by id, ip or email. If no parameters + * are passed to the method pre-existing session data is used. If $return is false + * this routine does not return on finding a banned user, it outputs a relevant + * message and stops execution. + * + * @param string|array $user_ips Can contain a string with one IP or an array of multiple IPs + */ + function check_ban($user_id = false, $user_ips = false, $user_email = false, $return = false) + { + global $config, $db; + + if (defined('IN_CHECK_BAN')) + { + return; + } + + $banned = false; + $cache_ttl = 3600; + $where_sql = array(); + + $sql = 'SELECT ban_ip, ban_userid, ban_email, ban_exclude, ban_give_reason, ban_end + FROM ' . BANLIST_TABLE . ' + WHERE '; + + // Determine which entries to check, only return those + if ($user_email === false) + { + $where_sql[] = "ban_email = ''"; + } + + if ($user_ips === false) + { + $where_sql[] = "(ban_ip = '' OR ban_exclude = 1)"; + } + + if ($user_id === false) + { + $where_sql[] = '(ban_userid = 0 OR ban_exclude = 1)'; + } + else + { + $cache_ttl = ($user_id == ANONYMOUS) ? 3600 : 0; + $_sql = '(ban_userid = ' . $user_id; + + if ($user_email !== false) + { + $_sql .= " OR ban_email <> ''"; + } + + if ($user_ips !== false) + { + $_sql .= " OR ban_ip <> ''"; + } + + $_sql .= ')'; + + $where_sql[] = $_sql; + } + + $sql .= (sizeof($where_sql)) ? implode(' AND ', $where_sql) : ''; + $result = $db->sql_query($sql, $cache_ttl); + + $ban_triggered_by = 'user'; + while ($row = $db->sql_fetchrow($result)) + { + if ($row['ban_end'] && $row['ban_end'] < time()) + { + continue; + } + + $ip_banned = false; + if (!empty($row['ban_ip'])) + { + if (!is_array($user_ips)) + { + $ip_banned = preg_match('#^' . str_replace('\*', '.*?', preg_quote($row['ban_ip'], '#')) . '$#i', $user_ips); + } + else + { + foreach ($user_ips as $user_ip) + { + if (preg_match('#^' . str_replace('\*', '.*?', preg_quote($row['ban_ip'], '#')) . '$#i', $user_ip)) + { + $ip_banned = true; + break; + } + } + } + } + + if ((!empty($row['ban_userid']) && intval($row['ban_userid']) == $user_id) || + $ip_banned || + (!empty($row['ban_email']) && preg_match('#^' . str_replace('\*', '.*?', preg_quote($row['ban_email'], '#')) . '$#i', $user_email))) + { + if (!empty($row['ban_exclude'])) + { + $banned = false; + break; + } + else + { + $banned = true; + $ban_row = $row; + + if (!empty($row['ban_userid']) && intval($row['ban_userid']) == $user_id) + { + $ban_triggered_by = 'user'; + } + else if ($ip_banned) + { + $ban_triggered_by = 'ip'; + } + else + { + $ban_triggered_by = 'email'; + } + + // Don't break. Check if there is an exclude rule for this user + } + } + } + $db->sql_freeresult($result); + + if ($banned && !$return) + { + global $template; + + // If the session is empty we need to create a valid one... + if (empty($this->session_id)) + { + // This seems to be no longer needed? - #14971 +// $this->session_create(ANONYMOUS); + } + + // Initiate environment ... since it won't be set at this stage + $this->setup(); + + // Logout the user, banned users are unable to use the normal 'logout' link + if ($this->data['user_id'] != ANONYMOUS) + { + $this->session_kill(); + } + + // We show a login box here to allow founders accessing the board if banned by IP + if (defined('IN_LOGIN') && $this->data['user_id'] == ANONYMOUS) + { + global $phpEx; + + $this->setup('ucp'); + $this->data['is_registered'] = $this->data['is_bot'] = false; + + // Set as a precaution to allow login_box() handling this case correctly as well as this function not being executed again. + define('IN_CHECK_BAN', 1); + + login_box("index.$phpEx"); + + // The false here is needed, else the user is able to circumvent the ban. + $this->session_kill(false); + } + + // Ok, we catch the case of an empty session id for the anonymous user... + // This can happen if the user is logging in, banned by username and the login_box() being called "again". + if (empty($this->session_id) && defined('IN_CHECK_BAN')) + { + $this->session_create(ANONYMOUS); + } + + + // Determine which message to output + $till_date = ($ban_row['ban_end']) ? $this->format_date($ban_row['ban_end']) : ''; + $message = ($ban_row['ban_end']) ? 'BOARD_BAN_TIME' : 'BOARD_BAN_PERM'; + + $message = sprintf($this->lang[$message], $till_date, '', ''); + $message .= ($ban_row['ban_give_reason']) ? '

' . sprintf($this->lang['BOARD_BAN_REASON'], $ban_row['ban_give_reason']) : ''; + $message .= '

' . $this->lang['BAN_TRIGGERED_BY_' . strtoupper($ban_triggered_by)] . ''; + + // To circumvent session_begin returning a valid value and the check_ban() not called on second page view, we kill the session again + $this->session_kill(false); + + // A very special case... we are within the cron script which is not supposed to print out the ban message... show blank page + if (defined('IN_CRON')) + { + garbage_collection(); + exit_handler(); + exit; + } + + trigger_error($message); + } + + return ($banned && $ban_row['ban_give_reason']) ? $ban_row['ban_give_reason'] : $banned; + } + + /** + * Check if ip is blacklisted + * This should be called only where absolutly necessary + * + * Only IPv4 (rbldns does not support AAAA records/IPv6 lookups) + * + * @author satmd (from the php manual) + * @param string $mode register/post - spamcop for example is ommitted for posting + * @return false if ip is not blacklisted, else an array([checked server], [lookup]) + */ + function check_dnsbl($mode, $ip = false) + { + if ($ip === false) + { + $ip = $this->ip; + } + + // Neither Spamhaus nor Spamcop supports IPv6 addresses. + if (strpos($ip, ':') !== false) + { + return false; + } + + $dnsbl_check = array( + 'sbl.spamhaus.org' => 'http://www.spamhaus.org/query/bl?ip=', + ); + + if ($mode == 'register') + { + $dnsbl_check['bl.spamcop.net'] = 'http://spamcop.net/bl.shtml?'; + } + + if ($ip) + { + $quads = explode('.', $ip); + $reverse_ip = $quads[3] . '.' . $quads[2] . '.' . $quads[1] . '.' . $quads[0]; + + // Need to be listed on all servers... + $listed = true; + $info = array(); + + foreach ($dnsbl_check as $dnsbl => $lookup) + { + if (phpbb_checkdnsrr($reverse_ip . '.' . $dnsbl . '.', 'A') === true) + { + $info = array($dnsbl, $lookup . $ip); + } + else + { + $listed = false; + } + } + + if ($listed) + { + return $info; + } + } + + return false; + } + + /** + * Check if URI is blacklisted + * This should be called only where absolutly necessary, for example on the submitted website field + * This function is not in use at the moment and is only included for testing purposes, it may not work at all! + * This means it is untested at the moment and therefore commented out + * + * @param string $uri URI to check + * @return true if uri is on blacklist, else false. Only blacklist is checked (~zero FP), no grey lists + function check_uribl($uri) + { + // Normally parse_url() is not intended to parse uris + // We need to get the top-level domain name anyway... change. + $uri = parse_url($uri); + + if ($uri === false || empty($uri['host'])) + { + return false; + } + + $uri = trim($uri['host']); + + if ($uri) + { + // One problem here... the return parameter for the "windows" method is different from what + // we expect... this may render this check useless... + if (phpbb_checkdnsrr($uri . '.multi.uribl.com.', 'A') === true) + { + return true; + } + } + + return false; + } + */ + + /** + * Set/Update a persistent login key + * + * This method creates or updates a persistent session key. When a user makes + * use of persistent (formerly auto-) logins a key is generated and stored in the + * DB. When they revisit with the same key it's automatically updated in both the + * DB and cookie. Multiple keys may exist for each user representing different + * browsers or locations. As with _any_ non-secure-socket no passphrase login this + * remains vulnerable to exploit. + */ + function set_login_key($user_id = false, $key = false, $user_ip = false) + { + global $config, $db; + + $user_id = ($user_id === false) ? $this->data['user_id'] : $user_id; + $user_ip = ($user_ip === false) ? $this->ip : $user_ip; + $key = ($key === false) ? (($this->cookie_data['k']) ? $this->cookie_data['k'] : false) : $key; + + $key_id = unique_id(hexdec(substr($this->session_id, 0, 8))); + + $sql_ary = array( + 'key_id' => (string) md5($key_id), + 'last_ip' => (string) $this->ip, + 'last_login' => (int) time() + ); + + if (!$key) + { + $sql_ary += array( + 'user_id' => (int) $user_id + ); + } + + if ($key) + { + $sql = 'UPDATE ' . SESSIONS_KEYS_TABLE . ' + SET ' . $db->sql_build_array('UPDATE', $sql_ary) . ' + WHERE user_id = ' . (int) $user_id . " + AND key_id = '" . $db->sql_escape(md5($key)) . "'"; + } + else + { + $sql = 'INSERT INTO ' . SESSIONS_KEYS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary); + } + $db->sql_query($sql); + + $this->cookie_data['k'] = $key_id; + + return false; + } + + /** + * Reset all login keys for the specified user + * + * This method removes all current login keys for a specified (or the current) + * user. It will be called on password change to render old keys unusable + */ + function reset_login_keys($user_id = false) + { + global $config, $db; + + $user_id = ($user_id === false) ? (int) $this->data['user_id'] : (int) $user_id; + + $sql = 'DELETE FROM ' . SESSIONS_KEYS_TABLE . ' + WHERE user_id = ' . (int) $user_id; + $db->sql_query($sql); + + // If the user is logged in, update last visit info first before deleting sessions + $sql = 'SELECT session_time, session_page + FROM ' . SESSIONS_TABLE . ' + WHERE session_user_id = ' . (int) $user_id . ' + ORDER BY session_time DESC'; + $result = $db->sql_query_limit($sql, 1); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if ($row) + { + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_lastvisit = ' . (int) $row['session_time'] . ", user_lastpage = '" . $db->sql_escape($row['session_page']) . "' + WHERE user_id = " . (int) $user_id; + $db->sql_query($sql); + } + + // Let's also clear any current sessions for the specified user_id + // If it's the current user then we'll leave this session intact + $sql_where = 'session_user_id = ' . (int) $user_id; + $sql_where .= ($user_id === (int) $this->data['user_id']) ? " AND session_id <> '" . $db->sql_escape($this->session_id) . "'" : ''; + + $sql = 'DELETE FROM ' . SESSIONS_TABLE . " + WHERE $sql_where"; + $db->sql_query($sql); + + // We're changing the password of the current user and they have a key + // Lets regenerate it to be safe + if ($user_id === (int) $this->data['user_id'] && $this->cookie_data['k']) + { + $this->set_login_key($user_id); + } + } + + + /** + * Check if the request originated from the same page. + * @param bool $check_script_path If true, the path will be checked as well + */ + function validate_referer($check_script_path = false) + { + global $config, $request; + + // no referer - nothing to validate, user's fault for turning it off (we only check on POST; so meta can't be the reason) + if (empty($this->referer) || empty($this->host)) + { + return true; + } + + $host = htmlspecialchars($this->host); + $ref = substr($this->referer, strpos($this->referer, '://') + 3); + + if (!(stripos($ref, $host) === 0) && (!$config['force_server_vars'] || !(stripos($ref, $config['server_name']) === 0))) + { + return false; + } + else if ($check_script_path && rtrim($this->page['root_script_path'], '/') !== '') + { + $ref = substr($ref, strlen($host)); + $server_port = $request->server('SERVER_PORT', 0); + + if ($server_port !== 80 && $server_port !== 443 && stripos($ref, ":$server_port") === 0) + { + $ref = substr($ref, strlen(":$server_port")); + } + + if (!(stripos(rtrim($ref, '/'), rtrim($this->page['root_script_path'], '/')) === 0)) + { + return false; + } + } + + return true; + } + + + function unset_admin() + { + global $db; + $sql = 'UPDATE ' . SESSIONS_TABLE . ' + SET session_admin = 0 + WHERE session_id = \'' . $db->sql_escape($this->session_id) . '\''; + $db->sql_query($sql); + } + + /** + * Update the session data + * + * @param array $session_data associative array of session keys to be updated + * @param string $session_id optional session_id, defaults to current user's session_id + */ + public function update_session($session_data, $session_id = null) + { + global $db; + + $session_id = ($session_id) ? $session_id : $this->session_id; + + $sql = 'UPDATE ' . SESSIONS_TABLE . ' SET ' . $db->sql_build_array('UPDATE', $session_data) . " + WHERE session_id = '" . $db->sql_escape($session_id) . "'"; + $db->sql_query($sql); + } +} diff --git a/phpBB/phpbb/style/extension_path_provider.php b/phpBB/phpbb/style/extension_path_provider.php new file mode 100644 index 0000000000..ec1d85f821 --- /dev/null +++ b/phpBB/phpbb/style/extension_path_provider.php @@ -0,0 +1,137 @@ +base_path_provider = $base_path_provider; + $this->phpbb_root_path = $phpbb_root_path; + } + + /** + * Sets a prefix for style paths searched within extensions. + * + * The prefix is inserted between the extension's path e.g. ext/foo/ and + * the looked up style path, e.g. styles/bar/. So it should not have a + * leading slash, but should have a trailing slash. + * + * @param string $ext_dir_prefix The prefix including trailing slash + * @return null + */ + public function set_ext_dir_prefix($ext_dir_prefix) + { + $this->ext_dir_prefix = $ext_dir_prefix; + } + + /** + * Finds style paths using the extension manager + * + * Locates a path (e.g. styles/prosilver/) in all active extensions. + * Then appends the core style paths based in the current working + * directory. + * + * @return array List of style paths + */ + public function find() + { + $directories = array(); + + $finder = $this->extension_manager->get_finder(); + foreach ($this->base_path_provider as $key => $paths) + { + if ($key == 'style') + { + foreach ($paths as $path) + { + $directories['style'][] = $path; + if ($path && !phpbb_is_absolute($path)) + { + // Remove phpBB root path from the style path, + // so the finder is able to find extension styles, + // when the root path is not ./ + if (strpos($path, $this->phpbb_root_path) === 0) + { + $path = substr($path, strlen($this->phpbb_root_path)); + } + + $result = $finder->directory('/' . $this->ext_dir_prefix . $path) + ->get_directories(true, false, true); + foreach ($result as $ext => $ext_path) + { + // Make sure $ext_path has no ending slash + if (substr($ext_path, -1) === '/') + { + $ext_path = substr($ext_path, 0, -1); + } + $directories[$ext][] = $ext_path; + } + } + } + } + } + + return $directories; + } + + /** + * Overwrites the current style paths + * + * @param array $styles An array of style paths. The first element is the main style. + * @return null + */ + public function set_styles(array $styles) + { + $this->base_path_provider->set_styles($styles); + $this->items = null; + } +} diff --git a/phpBB/phpbb/style/path_provider.php b/phpBB/phpbb/style/path_provider.php new file mode 100644 index 0000000000..731d682e88 --- /dev/null +++ b/phpBB/phpbb/style/path_provider.php @@ -0,0 +1,62 @@ +paths = array('style' => $styles); + } + + /** + * Retrieve an iterator over all style paths + * + * @return ArrayIterator An iterator for the array of style paths + */ + public function getIterator() + { + return new ArrayIterator($this->paths); + } +} diff --git a/phpBB/phpbb/style/path_provider_interface.php b/phpBB/phpbb/style/path_provider_interface.php new file mode 100644 index 0000000000..1a6153a4d3 --- /dev/null +++ b/phpBB/phpbb/style/path_provider_interface.php @@ -0,0 +1,42 @@ +set_default_template_path(); + } + + /** + * Sets the list of style paths + * + * These paths will be searched for style files in the provided order. + * Paths may be outside of phpBB, but templates loaded from these paths + * will still be cached. + * + * @param array $style_paths An array of paths to style directories + * @return null + */ + public function set_paths($style_paths) + { + $this->roots = array(); + $this->files = array(); + $this->filenames = array(); + + foreach ($style_paths as $key => $paths) + { + foreach ($paths as $path) + { + // Make sure $path has no ending slash + if (substr($path, -1) === '/') + { + $path = substr($path, 0, -1); + } + $this->roots[$key][] = $path; + } + } + } + + /** + * Sets the location of templates directory within style directories. + * + * The location must be a relative path, with a trailing slash. + * Typically it is one directory level deep, e.g. "template/". + * + * @param string $template_path Relative path to templates directory within style directories + * @return null + */ + public function set_template_path($template_path) + { + $this->template_path = $template_path; + } + + /** + * Sets the location of templates directory within style directories + * to the default, which is "template/". + * + * @return null + */ + public function set_default_template_path() + { + $this->template_path = 'template/'; + } + + /** + * {@inheritDoc} + */ + public function set_filenames(array $filename_array) + { + foreach ($filename_array as $handle => $filename) + { + if (empty($filename)) + { + trigger_error("style resource locator: set_filenames: Empty filename specified for $handle", E_USER_ERROR); + } + + $this->filename[$handle] = $filename; + + foreach ($this->roots as $root_key => $root_paths) + { + foreach ($root_paths as $root_index => $root) + { + $this->files[$root_key][$root_index][$handle] = $root . '/' . $this->template_path . $filename; + } + } + } + } + + /** + * {@inheritDoc} + */ + public function get_filename_for_handle($handle) + { + if (!isset($this->filename[$handle])) + { + trigger_error("style resource locator: get_filename_for_handle: No file specified for handle $handle", E_USER_ERROR); + } + return $this->filename[$handle]; + } + + /** + * {@inheritDoc} + */ + public function get_virtual_source_file_for_handle($handle) + { + // If we don't have a file assigned to this handle, die. + if (!isset($this->files['style'][0][$handle])) + { + trigger_error("style resource locator: No file specified for handle $handle", E_USER_ERROR); + } + + $source_file = $this->files['style'][0][$handle]; + return $source_file; + } + + /** + * {@inheritDoc} + */ + public function get_source_file_for_handle($handle, $find_all = false) + { + // If we don't have a file assigned to this handle, die. + if (!isset($this->files['style'][0][$handle])) + { + trigger_error("style resource locator: No file specified for handle $handle", E_USER_ERROR); + } + + // locate a source file that exists + $source_file = $this->files['style'][0][$handle]; + $tried = $source_file; + $found = false; + $found_all = array(); + foreach ($this->roots as $root_key => $root_paths) + { + foreach ($root_paths as $root_index => $root) + { + $source_file = $this->files[$root_key][$root_index][$handle]; + $tried .= ', ' . $source_file; + if (file_exists($source_file)) + { + $found = true; + break; + } + } + if ($found) + { + if ($find_all) + { + $found_all[] = $source_file; + $found = false; + } + else + { + break; + } + } + } + + // search failed + if (!$found && !$find_all) + { + trigger_error("style resource locator: File for handle $handle does not exist. Could not find: $tried", E_USER_ERROR); + } + + return ($find_all) ? $found_all : $source_file; + } + + /** + * {@inheritDoc} + */ + public function get_first_file_location($files, $return_default = false, $return_full_path = true) + { + // set default value + $default_result = false; + + // check all available paths + foreach ($this->roots as $root_paths) + { + foreach ($root_paths as $path) + { + // check all files + foreach ($files as $filename) + { + $source_file = $path . '/' . $filename; + if (file_exists($source_file)) + { + return ($return_full_path) ? $source_file : $filename; + } + + // assign first file as result if $return_default is true + if ($return_default && $default_result === false) + { + $default_result = $source_file; + } + } + } + } + + // search failed + return $default_result; + } + + /** + * Obtains filesystem path for a template file. + * + * The simplest use is specifying a single template file as a string + * in the first argument. This template file should be a basename + * of a template file in the selected style, or its parent styles + * if template inheritance is being utilized. + * + * Note: "selected style" is whatever style the style resource locator + * is configured for. + * + * The return value then will be a path, relative to the current + * directory or absolute, to the template file in the selected style + * or its closest parent. + * + * If the selected style does not have the template file being searched, + * (and if inheritance is involved, none of the parents have it either), + * false will be returned. + * + * Specifying true for $return_default will cause the function to + * return the first path which was checked for existence in the event + * that the template file was not found, instead of false. + * This is the path in the selected style itself, not any of its + * parents. + * + * $files can be given an array of templates instead of a single + * template. When given an array, the function will try to resolve + * each template in the array to a path, and will return the first + * path that exists, or false if none exist. + * + * If $files is an array and template inheritance is involved, first + * each of the files will be checked in the selected style, then each + * of the files will be checked in the immediate parent, and so on. + * + * If $return_full_path is false, then instead of returning a usable + * path (when the template is found) only the template's basename + * will be returned. This can be used to check which of the templates + * specified in $files exists. Naturally more than one template must + * be given in $files. + * + * This function works identically to get_first_file_location except + * it operates on a list of templates, not files. Practically speaking, + * the templates given in the first argument first are prepended with + * the template path (property in this class), then given to + * get_first_file_location for the rest of the processing. + * + * Templates given to this function can be relative paths for templates + * located in subdirectories of the template directories. The paths + * should be relative to the templates directory (template/ by default). + * + * @param string or array $files List of templates to locate. If there is only + * one template, $files can be a string to make code easier to read. + * @param bool $return_default Determines what to return if template does not + * exist. If true, function will return location where template is + * supposed to be. If false, function will return false. + * @param bool $return_full_path If true, function will return full path + * to template. If false, function will return template file name. + * This parameter can be used to check which one of set of template + * files is available. + * @return string or boolean Source template path if template exists or $return_default is + * true. False if template does not exist and $return_default is false + */ + public function get_first_template_location($templates, $return_default = false, $return_full_path = true) + { + // add template path prefix + $files = array(); + if (is_string($templates)) + { + $files[] = $this->template_path . $templates; + } + else + { + foreach ($templates as $template) + { + $files[] = $this->template_path . $template; + } + } + + return $this->get_first_file_location($files, $return_default, $return_full_path); + } +} diff --git a/phpBB/phpbb/style/style.php b/phpBB/phpbb/style/style.php new file mode 100644 index 0000000000..034f518091 --- /dev/null +++ b/phpBB/phpbb/style/style.php @@ -0,0 +1,241 @@ +phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + $this->config = $config; + $this->user = $user; + $this->locator = $locator; + $this->provider = $provider; + $this->template = $template; + } + + /** + * Get the style tree of the style preferred by the current user + * + * @return array Style tree, most specific first + */ + public function get_user_style() + { + $style_list = array( + $this->user->style['style_path'], + ); + + if ($this->user->style['style_parent_id']) + { + $style_list = array_merge($style_list, array_reverse(explode('/', $this->user->style['style_parent_tree']))); + } + + return $style_list; + } + + /** + * Set style location based on (current) user's chosen style. + * + * @param array $style_directories The directories to add style paths for + * E.g. array('ext/foo/bar/styles', 'styles') + * Default: array('styles') (phpBB's style directory) + * @return bool true + */ + public function set_style($style_directories = array('styles')) + { + $this->names = $this->get_user_style(); + + $paths = array(); + foreach ($style_directories as $directory) + { + foreach ($this->names as $name) + { + $path = $this->get_style_path($name, $directory); + + if (is_dir($path)) + { + $paths[] = $path; + } + } + } + + $this->provider->set_styles($paths); + $this->locator->set_paths($this->provider); + + $new_paths = array(); + foreach ($paths as $path) + { + $new_paths[] = $path . '/template/'; + } + + $this->template->set_style_names($this->names, $new_paths, ($style_directories === array('styles'))); + + return true; + } + + /** + * Set custom style location (able to use directory outside of phpBB). + * + * Note: Templates are still compiled to phpBB's cache directory. + * + * @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/). + * @return bool true + */ + 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); + + if ($template_path !== false) + { + $this->locator->set_template_path($template_path); + } + + $new_paths = array(); + foreach ($paths as $path) + { + $new_paths[] = $path . '/' . (($template_path !== false) ? $template_path : 'template/'); + } + + $this->template->set_style_names($names, $new_paths); + + return true; + } + + /** + * Get location of style directory for specific style_path + * + * @param string $path Style path, such as "prosilver" + * @param string $style_base_directory The base directory the style is in + * E.g. 'styles', 'ext/foo/bar/styles' + * Default: 'styles' + * @return string Path to style directory, relative to current path + */ + public function get_style_path($path, $style_base_directory = 'styles') + { + return $this->phpbb_root_path . trim($style_base_directory, '/') . '/' . $path; + } + + /** + * Defines a prefix to use for style paths in extensions + * + * @param string $ext_dir_prefix The prefix including trailing slash + * @return null + */ + public function set_ext_dir_prefix($ext_dir_prefix) + { + $this->provider->set_ext_dir_prefix($ext_dir_prefix); + } + + /** + * Locates source file path, accounting for styles tree and verifying that + * the path exists. + * + * @param string or array $files List of files to locate. If there is only + * one file, $files can be a string to make code easier to read. + * @param bool $return_default Determines what to return if file does not + * exist. If true, function will return location where file is + * supposed to be. If false, function will return false. + * @param bool $return_full_path If true, function will return full path + * to file. If false, function will return file name. This + * parameter can be used to check which one of set of files + * is available. + * @return string or boolean Source file path if file exists or $return_default is + * true. False if file does not exist and $return_default is false + */ + public function locate($files, $return_default = false, $return_full_path = true) + { + // convert string to array + if (is_string($files)) + { + $files = array($files); + } + + // use resource locator to find files + return $this->locator->get_first_file_location($files, $return_default, $return_full_path); + } +} diff --git a/phpBB/phpbb/template/asset.php b/phpBB/phpbb/template/asset.php new file mode 100644 index 0000000000..7c322cd971 --- /dev/null +++ b/phpBB/phpbb/template/asset.php @@ -0,0 +1,182 @@ +set_url($url); + } + + /** + * Set URL + * + * @param string $url URL + */ + public function set_url($url) + { + if (version_compare(PHP_VERSION, '5.4.7') < 0 && substr($url, 0, 2) === '//') + { + // Workaround for PHP 5.4.6 and older bug #62844 - add fake scheme and then remove it + $this->components = parse_url('http:' . $url); + $this->components['scheme'] = ''; + return; + } + $this->components = parse_url($url); + } + + /** + * Convert URL components into string + * + * @param array $components URL components + * @return string URL + */ + protected function join_url($components) + { + $path = ''; + if (isset($components['scheme'])) + { + $path = $components['scheme'] === '' ? '//' : $components['scheme'] . '://'; + } + + if (isset($components['user']) || isset($components['pass'])) + { + if ($path === '' && !isset($components['port'])) + { + $path = '//'; + } + $path .= $components['user']; + if (isset($components['pass'])) + { + $path .= ':' . $components['pass']; + } + $path .= '@'; + } + + if (isset($components['host'])) + { + if ($path === '' && !isset($components['port'])) + { + $path = '//'; + } + $path .= $components['host']; + if (isset($components['port'])) + { + $path .= ':' . $components['port']; + } + } + + if (isset($components['path'])) + { + $path .= $components['path']; + } + + if (isset($components['query'])) + { + $path .= '?' . $components['query']; + } + + if (isset($components['fragment'])) + { + $path .= '#' . $components['fragment']; + } + + return $path; + } + + /** + * Get URL + * + * @return string URL + */ + public function get_url() + { + return $this->join_url($this->components); + } + + /** + * Checks if URL is local and relative + * + * @return boolean True if URL is local and relative + */ + public function is_relative() + { + if (empty($this->components) || !isset($this->components['path'])) + { + // Invalid URL + return false; + } + return !isset($this->components['scheme']) && !isset($this->components['host']) && substr($this->components['path'], 0, 1) !== '/'; + } + + /** + * Get path component of current URL + * + * @return string Path + */ + public function get_path() + { + return isset($this->components['path']) ? $this->components['path'] : ''; + } + + /** + * Set path component + * + * @param string $path Path component + * @param boolean $urlencode If true, parts of path should be encoded with rawurlencode() + */ + public function set_path($path, $urlencode = false) + { + if ($urlencode) + { + $paths = explode('/', $path); + foreach ($paths as &$dir) + { + $dir = rawurlencode($dir); + } + $path = implode('/', $paths); + } + $this->components['path'] = $path; + } + + /** + * Add assets_version parameter to URL. + * Parameter will not be added if assets_version already exists in URL + * + * @param string $version Version + */ + public function add_assets_version($version) + { + if (!isset($this->components['query'])) + { + $this->components['query'] = 'assets_version=' . $version; + return; + } + $query = $this->components['query']; + if (!preg_match('/(^|[&;])assets_version=/', $query)) + { + $this->components['query'] = $query . '&assets_version=' . $version; + } + } +} diff --git a/phpBB/phpbb/template/context.php b/phpBB/phpbb/template/context.php new file mode 100644 index 0000000000..c5ce7422b9 --- /dev/null +++ b/phpBB/phpbb/template/context.php @@ -0,0 +1,389 @@ + $this->tpldata[block][iteration#][child][iteration#][child2][iteration#][variablename] == value + * if it's a root-level variable, it'll be like this: + * --> $this->tpldata[.][0][varname] == value + * + * @var array + */ + private $tpldata = array('.' => array(0 => array())); + + /** + * @var array Reference to template->tpldata['.'][0] + */ + private $rootref; + + public function __construct() + { + $this->clear(); + } + + /** + * Clears template data set. + */ + public function clear() + { + $this->tpldata = array('.' => array(0 => array())); + $this->rootref = &$this->tpldata['.'][0]; + } + + /** + * Assign a single scalar value to a single key. + * + * Value can be a string, an integer or a boolean. + * + * @param string $varname Variable name + * @param string $varval Value to assign to variable + */ + public function assign_var($varname, $varval) + { + $this->rootref[$varname] = $varval; + + return true; + } + + /** + * Append text to the string value stored in a key. + * + * Text is appended using the string concatenation operator (.). + * + * @param string $varname Variable name + * @param string $varval Value to append to variable + */ + public function append_var($varname, $varval) + { + $this->rootref[$varname] = (isset($this->rootref[$varname]) ? $this->rootref[$varname] : '') . $varval; + + return true; + } + + /** + * Returns a reference to template data array. + * + * This function is public so that template renderer may invoke it. + * Users should alter template variables via functions in phpbb_template. + * + * Note: modifying returned array will affect data stored in the context. + * + * @return array template data + */ + public function &get_data_ref() + { + // returning a reference directly is not + // something php is capable of doing + $ref = &$this->tpldata; + return $ref; + } + + /** + * Returns a reference to template root scope. + * + * This function is public so that template renderer may invoke it. + * Users should not need to invoke this function. + * + * Note: modifying returned array will affect data stored in the context. + * + * @return array template data + */ + public function &get_root_ref() + { + // rootref is already a reference + return $this->rootref; + } + + /** + * Assign key variable pairs from an array to a specified block + * + * @param string $blockname Name of block to assign $vararray to + * @param array $vararray A hash of variable name => value pairs + */ + public function assign_block_vars($blockname, array $vararray) + { + if (strpos($blockname, '.') !== false) + { + // Nested block. + $blocks = explode('.', $blockname); + $blockcount = sizeof($blocks) - 1; + + $str = &$this->tpldata; + for ($i = 0; $i < $blockcount; $i++) + { + $str = &$str[$blocks[$i]]; + $str = &$str[sizeof($str) - 1]; + } + + $s_row_count = isset($str[$blocks[$blockcount]]) ? sizeof($str[$blocks[$blockcount]]) : 0; + $vararray['S_ROW_COUNT'] = $vararray['S_ROW_NUM'] = $s_row_count; + + // Assign S_FIRST_ROW + if (!$s_row_count) + { + $vararray['S_FIRST_ROW'] = true; + } + + // Assign S_BLOCK_NAME + $vararray['S_BLOCK_NAME'] = $blocks[$blockcount]; + + // Now the tricky part, we always assign S_LAST_ROW and remove the entry before + // This is much more clever than going through the complete template data on display (phew) + $vararray['S_LAST_ROW'] = true; + if ($s_row_count > 0) + { + unset($str[$blocks[$blockcount]][($s_row_count - 1)]['S_LAST_ROW']); + } + + // Now we add the block that we're actually assigning to. + // We're adding a new iteration to this block with the given + // variable assignments. + $str[$blocks[$blockcount]][] = $vararray; + + // Set S_NUM_ROWS + foreach ($str[$blocks[$blockcount]] as &$mod_block) + { + $mod_block['S_NUM_ROWS'] = sizeof($str[$blocks[$blockcount]]); + } + } + else + { + // Top-level block. + $s_row_count = (isset($this->tpldata[$blockname])) ? sizeof($this->tpldata[$blockname]) : 0; + $vararray['S_ROW_COUNT'] = $vararray['S_ROW_NUM'] = $s_row_count; + + // Assign S_FIRST_ROW + if (!$s_row_count) + { + $vararray['S_FIRST_ROW'] = true; + } + + // Assign S_BLOCK_NAME + $vararray['S_BLOCK_NAME'] = $blockname; + + // We always assign S_LAST_ROW and remove the entry before + $vararray['S_LAST_ROW'] = true; + if ($s_row_count > 0) + { + unset($this->tpldata[$blockname][($s_row_count - 1)]['S_LAST_ROW']); + } + + // Add a new iteration to this block with the variable assignments we were given. + $this->tpldata[$blockname][] = $vararray; + + // Set S_NUM_ROWS + foreach ($this->tpldata[$blockname] as &$mod_block) + { + $mod_block['S_NUM_ROWS'] = sizeof($this->tpldata[$blockname]); + } + } + + return true; + } + + /** + * Change already assigned key variable pair (one-dimensional - single loop entry) + * + * An example of how to use this function: + * {@example alter_block_array.php} + * + * @param string $blockname the blockname, for example 'loop' + * @param array $vararray the var array to insert/add or merge + * @param mixed $key Key to search for + * + * array: KEY => VALUE [the key/value pair to search for within the loop to determine the correct position] + * + * int: Position [the position to change or insert at directly given] + * + * If key is false the position is set to 0 + * If key is true the position is set to the last entry + * + * @param string $mode Mode to execute (valid modes are 'insert' and 'change') + * + * If insert, the vararray is inserted at the given position (position counting from zero). + * If change, the current block gets merged with the vararray (resulting in new key/value pairs be added and existing keys be replaced by the new value). + * + * Since counting begins by zero, inserting at the last position will result in this array: array(vararray, last positioned array) + * and inserting at position 1 will result in this array: array(first positioned array, vararray, following vars) + * + * @return bool false on error, true on success + */ + public function alter_block_array($blockname, array $vararray, $key = false, $mode = 'insert') + { + if (strpos($blockname, '.') !== false) + { + // Nested block. + $blocks = explode('.', $blockname); + $blockcount = sizeof($blocks) - 1; + + $block = &$this->tpldata; + for ($i = 0; $i < $blockcount; $i++) + { + if (($pos = strpos($blocks[$i], '[')) !== false) + { + $name = substr($blocks[$i], 0, $pos); + + if (strpos($blocks[$i], '[]') === $pos) + { + $index = sizeof($block[$name]) - 1; + } + else + { + $index = min((int) substr($blocks[$i], $pos + 1, -1), sizeof($block[$name]) - 1); + } + } + else + { + $name = $blocks[$i]; + $index = sizeof($block[$name]) - 1; + } + $block = &$block[$name]; + $block = &$block[$index]; + } + + $block = &$block[$blocks[$i]]; // Traverse the last block + } + else + { + // Top-level block. + $block = &$this->tpldata[$blockname]; + } + + // Change key to zero (change first position) if false and to last position if true + if ($key === false || $key === true) + { + $key = ($key === false) ? 0 : sizeof($block); + } + + // Get correct position if array given + if (is_array($key)) + { + // Search array to get correct position + list($search_key, $search_value) = @each($key); + + $key = NULL; + foreach ($block as $i => $val_ary) + { + if ($val_ary[$search_key] === $search_value) + { + $key = $i; + break; + } + } + + // key/value pair not found + if ($key === NULL) + { + return false; + } + } + + // Insert Block + if ($mode == 'insert') + { + // Make sure we are not exceeding the last iteration + if ($key >= sizeof($this->tpldata[$blockname])) + { + $key = sizeof($this->tpldata[$blockname]); + unset($this->tpldata[$blockname][($key - 1)]['S_LAST_ROW']); + $vararray['S_LAST_ROW'] = true; + } + else if ($key === 0) + { + unset($this->tpldata[$blockname][0]['S_FIRST_ROW']); + $vararray['S_FIRST_ROW'] = true; + } + + // Assign S_BLOCK_NAME + $vararray['S_BLOCK_NAME'] = $blockname; + + // Re-position template blocks + for ($i = sizeof($block); $i > $key; $i--) + { + $block[$i] = $block[$i-1]; + + $block[$i]['S_ROW_COUNT'] = $block[$i]['S_ROW_NUM'] = $i; + } + + // Insert vararray at given position + $block[$key] = $vararray; + $block[$key]['S_ROW_COUNT'] = $block[$key]['S_ROW_NUM'] = $key; + + // Set S_NUM_ROWS + foreach ($this->tpldata[$blockname] as &$mod_block) + { + $mod_block['S_NUM_ROWS'] = sizeof($this->tpldata[$blockname]); + } + + return true; + } + + // Which block to change? + if ($mode == 'change') + { + if ($key == sizeof($block)) + { + $key--; + } + + $block[$key] = array_merge($block[$key], $vararray); + + return true; + } + + return false; + } + + /** + * Reset/empty complete block + * + * @param string $blockname Name of block to destroy + */ + public function destroy_block_vars($blockname) + { + if (strpos($blockname, '.') !== false) + { + // Nested block. + $blocks = explode('.', $blockname); + $blockcount = sizeof($blocks) - 1; + + $str = &$this->tpldata; + for ($i = 0; $i < $blockcount; $i++) + { + $str = &$str[$blocks[$i]]; + $str = &$str[sizeof($str) - 1]; + } + + unset($str[$blocks[$blockcount]]); + } + else + { + // Top-level block. + unset($this->tpldata[$blockname]); + } + + return true; + } +} diff --git a/phpBB/phpbb/template/locator.php b/phpBB/phpbb/template/locator.php new file mode 100644 index 0000000000..f6fd20bcc2 --- /dev/null +++ b/phpBB/phpbb/template/locator.php @@ -0,0 +1,163 @@ + filename pairs. + * + * @param array $filename_array Should be a hash of handle => filename pairs. + */ + public function set_filenames(array $filename_array); + + /** + * Determines the filename for a template handle. + * + * The filename comes from array used in a set_filenames call, + * which should have been performed prior to invoking this function. + * Return value is a file basename (without path). + * + * @param $handle string Template handle + * @return string Filename corresponding to the template handle + */ + public function get_filename_for_handle($handle); + + /** + * Determines the source file path for a template handle without + * regard for styles tree. + * + * This function returns the path in "primary" style directory + * corresponding to the given template handle. That path may or + * may not actually exist on the filesystem. Because this function + * does not perform stat calls to determine whether the path it + * 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 + * directory if primary style has parent styles). + * + * This function will trigger an error if the handle was never + * associated with a template file via set_filenames. + * + * @param $handle string Template handle + * @return string Path to source file path in primary style directory + */ + public function get_virtual_source_file_for_handle($handle); + + /** + * Determines the source file path for a template handle, accounting + * for styles tree and verifying that the path exists. + * + * This function returns the actual path that may be compiled for + * the specified template handle. It will trigger an error if + * the template handle was never associated with a template path + * via set_filenames or if the template file does not exist on the + * filesystem. + * + * Use get_virtual_source_file_for_handle to just resolve a template + * handle to a path without any filesystem or styles tree checks. + * + * @param string $handle Template handle (i.e. "friendly" template name) + * @param bool $find_all If true, each root path will be checked and function + * will return array of files instead of string and will not + * trigger a error if template does not exist + * @return string Source file path + */ + public function get_source_file_for_handle($handle, $find_all = false); + + /** + * Obtains a complete filesystem path for a file in a style. + * + * This function traverses the style tree (selected style and + * its parents in order, if inheritance is being used) and finds + * the first file on the filesystem matching specified relative path, + * or the first of the specified paths if more than one path is given. + * + * This function can be used to determine filesystem path of any + * file under any style, with the consequence being that complete + * relative to the style directory path must be provided as an argument. + * + * In particular, this function can be used to locate templates + * and javascript files. + * + * For locating templates get_first_template_location should be used + * as it prepends the configured template path to the template basename. + * + * Note: "selected style" is whatever style the style resource locator + * is configured for. + * + * The return value then will be a path, relative to the current + * directory or absolute, to the first existing file in the selected + * style or its closest parent. + * + * If the selected style does not have the file being searched, + * (and if inheritance is involved, none of the parents have it either), + * false will be returned. + * + * Multiple files can be specified, in which case the first file in + * the list that can be found on the filesystem is returned. + * + * If multiple files are specified and inheritance is involved, + * first each of the specified files is checked in the selected style, + * then each of the specified files is checked in the immediate parent, + * etc. + * + * Specifying true for $return_default will cause the function to + * return the first path which was checked for existence in the event + * that the template file was not found, instead of false. + * This is always a path in the selected style itself, not any of its + * parents. + * + * If $return_full_path is false, then instead of returning a usable + * path (when the file is found) the file's path relative to the style + * directory will be returned. This is the same path as was given to + * the function as a parameter. This can be used to check which of the + * files specified in $files exists. Naturally this requires passing + * more than one file in $files. + * + * @param array $files List of files to locate. + * @param bool $return_default Determines what to return if file does not + * exist. If true, function will return location where file is + * supposed to be. If false, function will return false. + * @param bool $return_full_path If true, function will return full path + * to file. If false, function will return file name. This + * parameter can be used to check which one of set of files + * is available. + * @return string or boolean Source file path if file exists or $return_default is + * true. False if file does not exist and $return_default is false + */ + public function get_first_file_location($files, $return_default = false, $return_full_path = true); +} diff --git a/phpBB/phpbb/template/template.php b/phpBB/phpbb/template/template.php new file mode 100644 index 0000000000..89a01e924d --- /dev/null +++ b/phpBB/phpbb/template/template.php @@ -0,0 +1,157 @@ + filename pairs. + * @return phpbb_template $this + */ + public function set_filenames(array $filename_array); + + /** + * Sets the style names/paths corresponding to style hierarchy being compiled + * and/or rendered. + * + * @param array $style_names List of style names in inheritance tree order + * @param array $style_paths List of style paths in inheritance tree order + * @return phpbb_template $this + */ + public function set_style_names(array $style_names, array $style_paths); + + /** + * Clears all variables and blocks assigned to this template. + * + * @return phpbb_template $this + */ + public function destroy(); + + /** + * Reset/empty complete block + * + * @param string $blockname Name of block to destroy + * @return phpbb_template $this + */ + public function destroy_block_vars($blockname); + + /** + * Display a template for provided handle. + * + * The template will be loaded and compiled, if necessary, first. + * + * This function calls hooks. + * + * @param string $handle Handle to display + * @return phpbb_template $this + */ + public function display($handle); + + /** + * Display the handle and assign the output to a template variable + * or return the compiled result. + * + * @param string $handle Handle to operate on + * @param string $template_var Template variable to assign compiled handle to + * @param bool $return_content If true return compiled handle, otherwise assign to $template_var + * @return phpbb_template|string if $return_content is true return string of the compiled handle, otherwise return $this + */ + public function assign_display($handle, $template_var = '', $return_content = true); + + /** + * Assign key variable pairs from an array + * + * @param array $vararray A hash of variable name => value pairs + * @return phpbb_template $this + */ + public function assign_vars(array $vararray); + + /** + * Assign a single scalar value to a single key. + * + * Value can be a string, an integer or a boolean. + * + * @param string $varname Variable name + * @param string $varval Value to assign to variable + * @return phpbb_template $this + */ + public function assign_var($varname, $varval); + + /** + * Append text to the string value stored in a key. + * + * Text is appended using the string concatenation operator (.). + * + * @param string $varname Variable name + * @param string $varval Value to append to variable + * @return phpbb_template $this + */ + public function append_var($varname, $varval); + + /** + * Assign key variable pairs from an array to a specified block + * @param string $blockname Name of block to assign $vararray to + * @param array $vararray A hash of variable name => value pairs + * @return phpbb_template $this + */ + public function assign_block_vars($blockname, array $vararray); + + /** + * Change already assigned key variable pair (one-dimensional - single loop entry) + * + * An example of how to use this function: + * {@example alter_block_array.php} + * + * @param string $blockname the blockname, for example 'loop' + * @param array $vararray the var array to insert/add or merge + * @param mixed $key Key to search for + * + * array: KEY => VALUE [the key/value pair to search for within the loop to determine the correct position] + * + * int: Position [the position to change or insert at directly given] + * + * If key is false the position is set to 0 + * If key is true the position is set to the last entry + * + * @param string $mode Mode to execute (valid modes are 'insert' and 'change') + * + * If insert, the vararray is inserted at the given position (position counting from zero). + * If change, the current block gets merged with the vararray (resulting in new key/value pairs be added and existing keys be replaced by the new value). + * + * Since counting begins by zero, inserting at the last position will result in this array: array(vararray, last positioned array) + * and inserting at position 1 will result in this array: array(first positioned array, vararray, following vars) + * + * @return bool false on error, true on success + */ + public function alter_block_array($blockname, array $vararray, $key = false, $mode = 'insert'); + + /** + * Get path to template for handle (required for BBCode parser) + * + * @return string + */ + public function get_source_file_for_handle($handle); +} diff --git a/phpBB/phpbb/template/twig/definition.php b/phpBB/phpbb/template/twig/definition.php new file mode 100644 index 0000000000..6557b209eb --- /dev/null +++ b/phpBB/phpbb/template/twig/definition.php @@ -0,0 +1,69 @@ +definitions[$name])) ? $this->definitions[$name] : null; + } + + /** + * DEFINE a variable + * + * @param string $name + * @param mixed $value + * @return phpbb_template_twig_definition + */ + public function set($name, $value) + { + $this->definitions[$name] = $value; + + return $this; + } + + /** + * Append to a variable + * + * @param string $name + * @param string $value + * @return phpbb_template_twig_definition + */ + public function append($name, $value) + { + if (!isset($this->definitions[$name])) + { + $this->definitions[$name] = ''; + } + + $this->definitions[$name] .= $value; + + return $this; + } +} diff --git a/phpBB/phpbb/template/twig/environment.php b/phpBB/phpbb/template/twig/environment.php new file mode 100644 index 0000000000..b60cd72325 --- /dev/null +++ b/phpBB/phpbb/template/twig/environment.php @@ -0,0 +1,140 @@ + path) + * @param string $phpbb_root_path + * @param Twig_LoaderInterface $loader + * @param array $options Array of options to pass to Twig + */ + public function __construct($phpbb_config, $phpbb_extensions, $phpbb_root_path, Twig_LoaderInterface $loader = null, $options = array()) + { + $this->phpbb_config = $phpbb_config; + $this->phpbb_extensions = $phpbb_extensions; + $this->phpbb_root_path = $phpbb_root_path; + + return parent::__construct($loader, $options); + } + + /** + * Get the list of enabled phpBB extensions + * + * Used in EVENT node + * + * @return array + */ + public function get_phpbb_extensions() + { + return $this->phpbb_extensions; + } + + /** + * Get phpBB config + * + * @return phpbb_config + */ + public function get_phpbb_config() + { + return $this->phpbb_config; + } + + /** + * Get the phpBB root path + * + * @return string + */ + public function get_phpbb_root_path() + { + return $this->phpbb_root_path; + } + + /** + * Get the namespace look up order + * + * @return array + */ + public function getNamespaceLookUpOrder() + { + return $this->namespace_look_up_order; + } + + /** + * Set the namespace look up order to load templates from + * + * @param array $namespace + * @return Twig_Environment + */ + public function setNamespaceLookUpOrder($namespace) + { + $this->namespace_look_up_order = $namespace; + + return $this; + } + + /** + * Loads a template by name. + * + * @param string $name The template name + * @param integer $index The index if it is an embedded template + * @return Twig_TemplateInterface A template instance representing the given template name + */ + public function loadTemplate($name, $index = null) + { + if (strpos($name, '@') === false) + { + foreach ($this->getNamespaceLookUpOrder() as $namespace) + { + try + { + if ($namespace === '__main__') + { + return parent::loadTemplate($name, $index); + } + + return parent::loadTemplate('@' . $namespace . '/' . $name, $index); + } + catch (Twig_Error_Loader $e) + { + } + } + + // We were unable to load any templates + throw $e; + } + else + { + return parent::loadTemplate($name, $index); + } + } +} diff --git a/phpBB/phpbb/template/twig/extension.php b/phpBB/phpbb/template/twig/extension.php new file mode 100644 index 0000000000..c279726434 --- /dev/null +++ b/phpBB/phpbb/template/twig/extension.php @@ -0,0 +1,188 @@ +context = $context; + $this->user = $user; + } + + /** + * Get the name of this extension + * + * @return string + */ + public function getName() + { + return 'phpbb'; + } + + /** + * Returns the token parser instance to add to the existing list. + * + * @return array An array of Twig_TokenParser instances + */ + public function getTokenParsers() + { + return array( + new phpbb_template_twig_tokenparser_define, + new phpbb_template_twig_tokenparser_include, + new phpbb_template_twig_tokenparser_includejs, + new phpbb_template_twig_tokenparser_includecss, + new phpbb_template_twig_tokenparser_event, + new phpbb_template_twig_tokenparser_includephp, + new phpbb_template_twig_tokenparser_php, + ); + } + + /** + * Returns a list of filters to add to the existing list. + * + * @return array An array of filters + */ + public function getFilters() + { + return array( + new Twig_SimpleFilter('subset', array($this, 'loop_subset'), array('needs_environment' => true)), + new Twig_SimpleFilter('addslashes', 'addslashes'), + ); + } + + /** + * Returns a list of global functions to add to the existing list. + * + * @return array An array of global functions + */ + public function getFunctions() + { + return array( + new Twig_SimpleFunction('lang', array($this, 'lang')), + ); + } + + /** + * Returns a list of operators to add to the existing list. + * + * @return array An array of operators + */ + public function getOperators() + { + return array( + array( + '!' => array('precedence' => 50, 'class' => 'Twig_Node_Expression_Unary_Not'), + ), + array( + // precedence settings are copied from similar operators in Twig core extension + '||' => array('precedence' => 10, 'class' => 'Twig_Node_Expression_Binary_Or', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + '&&' => array('precedence' => 15, 'class' => 'Twig_Node_Expression_Binary_And', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + + 'eq' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Equal', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + + 'ne' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_NotEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + 'neq' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_NotEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + '<>' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_NotEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + + '===' => array('precedence' => 20, 'class' => 'phpbb_template_twig_node_expression_binary_equalequal', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + '!==' => array('precedence' => 20, 'class' => 'phpbb_template_twig_node_expression_binary_notequalequal', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + + 'gt' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Greater', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + 'gte' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_GreaterEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + 'ge' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_GreaterEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + 'lt' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Less', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + 'lte' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_LessEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + 'le' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_LessEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + + 'mod' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Mod', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + ), + ); + } + + /** + * Grabs a subset of a loop + * + * @param Twig_Environment $env A Twig_Environment instance + * @param mixed $item A variable + * @param integer $start Start of the subset + * @param integer $end End of the subset + * @param Boolean $preserveKeys Whether to preserve key or not (when the input is an array) + * + * @return mixed The sliced variable + */ + function loop_subset(Twig_Environment $env, $item, $start, $end = null, $preserveKeys = false) + { + // We do almost the same thing as Twig's slice (array_slice), except when $end is positive + if ($end >= 1) + { + // When end is > 1, subset will end on the last item in an array with the specified $end + // This is different from slice in that it is the number we end on rather than the number + // of items to grab (length) + + // Start must always be the actual starting number for this calculation (not negative) + $start = ($start < 0) ? sizeof($item) + $start : $start; + $end = $end - $start; + } + + // We always include the last element (this was the past design) + $end = ($end == -1 || $end === null) ? null : $end + 1; + + return twig_slice($env, $item, $start, $end, $preserveKeys); + } + + /** + * Get output for a language variable (L_FOO, LA_FOO) + * + * This function checks to see if the language var was outputted to $context + * (e.g. in the ACP, L_TITLE) + * If not, we return the result of $user->lang() + * + * @param string $lang name + * @return string + */ + function lang() + { + $args = func_get_args(); + $key = $args[0]; + + $context = $this->context->get_data_ref(); + $context_vars = $context['.'][0]; + + if (isset($context_vars['L_' . $key])) + { + return $context_vars['L_' . $key]; + } + + // LA_ is transformed into lang(\'$1\')|addslashes, so we should not + // need to check for it + + return call_user_func_array(array($this->user, 'lang'), $args); + } +} diff --git a/phpBB/phpbb/template/twig/lexer.php b/phpBB/phpbb/template/twig/lexer.php new file mode 100644 index 0000000000..46412ad048 --- /dev/null +++ b/phpBB/phpbb/template/twig/lexer.php @@ -0,0 +1,300 @@ +fix_inline_variable_tokens(array( + 'DEFINE.+=', + 'INCLUDE', + 'INCLUDEPHP', + 'INCLUDEJS', + 'INCLUDECSS', + ), $code); + + // Fix our BEGIN statements + $code = $this->fix_begin_tokens($code); + + // Fix our IF tokens + $code = $this->fix_if_tokens($code); + + // Fix our DEFINE tokens + $code = $this->fix_define_tokens($code); + + // Replace all of our starting tokens, with Twig style, {% TOKEN %} + // This also strips outer parenthesis, becomes + $code = preg_replace('##', '{% $1 $2 %}', $code); + + // Replace all of our twig masks with Twig code (e.g. with {% block $1 %}) + $code = $this->replace_twig_tag_masks($code, $twig_tags); + + // Replace all of our language variables, {L_VARNAME}, with Twig style, {{ lang('NAME') }} + // Appends any filters after lang() + $code = preg_replace('#{L_([a-zA-Z0-9_\.]+)(\|[^}]+?)?}#', '{{ lang(\'$1\')$2 }}', $code); + + // Replace all of our escaped language variables, {LA_VARNAME}, with Twig style, {{ lang('NAME')|addslashes }} + // Appends any filters after lang(), but before addslashes + $code = preg_replace('#{LA_([a-zA-Z0-9_\.]+)(\|[^}]+?)?}#', '{{ lang(\'$1\')$2|addslashes }}', $code); + + // Replace all of our variables, {VARNAME}, with Twig style, {{ VARNAME }} + // Appends any filters + $code = preg_replace('#{([a-zA-Z0-9_\.]+)(\|[^}]+?)?}#', '{{ $1$2 }}', $code); + + return parent::tokenize($code, $filename); + } + + /** + * Fix tokens that may have inline variables + * + * E.g. "; + }; + + return preg_replace_callback('##', $callback, $code); + } + + /** + * Fix begin tokens (convert our BEGIN to Twig for) + * + * Not meant to be used outside of this context, public because the anonymous function calls this + * + * @param string $code + * @param array $parent_nodes (used in recursion) + * @return string + */ + public function fix_begin_tokens($code, $parent_nodes = array()) + { + // PHP 5.3 cannot use $this in an anonymous function, so use this as a work-around + $parent_class = $this; + $callback = function ($matches) use ($parent_class, $parent_nodes) + { + $name = $matches[1]; + $subset = trim(substr($matches[2], 1, -1)); // Remove parenthesis + $body = $matches[3]; + + // Is the designer wanting to call another loop in a loop? + // + // + // + // + // 'loop2' is actually on the same nesting level as 'loop' you assign + // variables to it with template->assign_block_vars('loop2', array(...)) + if (strpos($name, '!') === 0) + { + // Count the number if ! occurrences + $count = substr_count($name, '!'); + for ($i = 0; $i < $count; $i++) + { + array_pop($parent_nodes); + $name = substr($name, 1); + } + } + + // Remove all parent nodes, e.g. foo, bar from foo.bar.foobar.VAR + foreach ($parent_nodes as $node) + { + $body = preg_replace('#([^a-zA-Z0-9_])' . $node . '\.([a-zA-Z0-9_]+)\.#', '$1$2.', $body); + } + + // Add current node to list of parent nodes for child nodes + $parent_nodes[] = $name; + + // Recursive...fix any child nodes + $body = $parent_class->fix_begin_tokens($body, $parent_nodes); + + // Rename loopname vars (to prevent collisions, loop children are named (loop name)_loop_element) + $body = str_replace($name . '.', $name . '_loop_element.', $body); + + // Need the parent variable name + array_pop($parent_nodes); + $parent = (!empty($parent_nodes)) ? end($parent_nodes) . '_loop_element.' : ''; + + if ($subset !== '') + { + $subset = '|subset(' . $subset . ')'; + } + + // Turn into a Twig for loop, using (loop name)_loop_element for each child + return "{% for {$name}_loop_element in {$parent}{$name}{$subset} %}{$body}{% endfor %}"; + }; + + // Replace correctly, only needs to be done once + $code = str_replace('', '{% else %}', $code); + + return preg_replace_callback('#(.+?)#s', $callback, $code); + } + + /** + * Fix IF statements + * + * @param string $code + * @return string + */ + protected function fix_if_tokens($code) + { + $callback = function($matches) + { + // Replace $TEST with definition.TEST + $matches[1] = preg_replace('#\s\$([a-zA-Z_0-9]+)#', ' definition.$1', $matches[1]); + + // Replace .test with test|length + $matches[1] = preg_replace('#\s\.([a-zA-Z_0-9\.]+)#', ' $1|length', $matches[1]); + + return ''; + }; + + // Replace our "div by" with Twig's divisibleby (Twig does not like test names with spaces) + $code = preg_replace('# div by ([0-9]+)#', ' divisibleby($1)', $code); + + return preg_replace_callback('##', $callback, $code); + } + + /** + * Fix DEFINE statements and {$VARNAME} variables + * + * @param string $code + * @return string + */ + protected function fix_define_tokens($code) + { + /** + * Changing $VARNAME to definition.varname because set is only local + * context (e.g. DEFINE $TEST will only make $TEST available in current + * template and any child templates, but not any parent templates). + * + * DEFINE handles setting it properly to definition in its node, but the + * variables reading FROM it need to be altered to definition.VARNAME + * + * Setting up definition as a class in the array passed to Twig + * ($context) makes set definition.TEST available in the global context + */ + + // Replace #', '{% DEFINE $1 %}', $code); + + // Changing UNDEFINE NAME to DEFINE NAME = null to save from creating an extra token parser/node + $code = preg_replace('##', '{% DEFINE $1= null %}', $code); + + // Replace all of our variables, {$VARNAME}, with Twig style, {{ definition.VARNAME }} + $code = preg_replace('#{\$([a-zA-Z0-9_\.]+)}#', '{{ definition.$1 }}', $code); + + // Replace all of our variables, ~ $VARNAME ~, with Twig style, ~ definition.VARNAME ~ + $code = preg_replace('#~ \$([a-zA-Z0-9_\.]+) ~#', '~ definition.$1 ~', $code); + + return $code; + } + + /** + * Replace Twig tag masks with Twig tag calls + * + * E.g. with {% block foo %} + * + * @param string $code + * @param array $twig_tags All tags we want to create a mask for + * @return string + */ + protected function replace_twig_tag_masks($code, $twig_tags) + { + $callback = function ($matches) + { + $matches[1] = strtolower($matches[1]); + + return "{% {$matches[1]}{$matches[2]}%}"; + }; + + foreach ($twig_tags as &$tag) + { + $tag = strtoupper($tag); + } + + // twig_tags is an array of the twig tags, which are all lowercase, but we use all uppercase tags + $code = preg_replace_callback('##',$callback, $code); + + return $code; + } +} diff --git a/phpBB/phpbb/template/twig/node/define.php b/phpBB/phpbb/template/twig/node/define.php new file mode 100644 index 0000000000..fcb19cc773 --- /dev/null +++ b/phpBB/phpbb/template/twig/node/define.php @@ -0,0 +1,58 @@ + $name, 'value' => $value), array('capture' => $capture, 'safe' => false), $lineno, $tag); + } + + /** + * Compiles the node to PHP. + * + * @param Twig_Compiler A Twig_Compiler instance + */ + public function compile(Twig_Compiler $compiler) + { + $compiler->addDebugInfo($this); + + if ($this->getAttribute('capture')) { + $compiler + ->write("ob_start();\n") + ->subcompile($this->getNode('value')) + ; + + $compiler->write("\$value = ('' === \$value = ob_get_clean()) ? '' : new Twig_Markup(\$value, \$this->env->getCharset());\n"); + } + else + { + $compiler + ->write("\$value = ") + ->subcompile($this->getNode('value')) + ->raw(";\n") + ; + } + + $compiler + ->write("\$context['definition']->set('") + ->raw($this->getNode('name')->getAttribute('name')) + ->raw("', \$value);\n") + ; + } +} diff --git a/phpBB/phpbb/template/twig/node/event.php b/phpBB/phpbb/template/twig/node/event.php new file mode 100644 index 0000000000..971dea14fa --- /dev/null +++ b/phpBB/phpbb/template/twig/node/event.php @@ -0,0 +1,79 @@ +environment = $environment; + + parent::__construct(array('expr' => $expr), array(), $lineno, $tag); + } + + /** + * Compiles the node to PHP. + * + * @param Twig_Compiler A Twig_Compiler instance + */ + public function compile(Twig_Compiler $compiler) + { + $compiler->addDebugInfo($this); + + $location = $this->getNode('expr')->getAttribute('name'); + + foreach ($this->environment->get_phpbb_extensions() as $ext_namespace => $ext_path) + { + $ext_namespace = str_replace('/', '_', $ext_namespace); + + if (defined('DEBUG')) + { + // If debug mode is enabled, lets check for new/removed EVENT + // templates on page load rather than at compile. This is + // slower, but makes developing extensions easier (no need to + // purge the cache when a new event template file is added) + $compiler + ->write("if (\$this->env->getLoader()->exists('@{$ext_namespace}/{$location}.html')) {\n") + ->indent() + ; + } + + if (defined('DEBUG') || $this->environment->getLoader()->exists('@' . $ext_namespace . '/' . $location . '.html')) + { + $compiler + ->write("\$previous_look_up_order = \$this->env->getNamespaceLookUpOrder();\n") + + // We set the namespace lookup order to be this extension first, then the main path + ->write("\$this->env->setNamespaceLookUpOrder(array('{$ext_namespace}', '__main__'));\n") + ->write("\$this->env->loadTemplate('@{$ext_namespace}/{$location}.html')->display(\$context);\n") + ->write("\$this->env->setNamespaceLookUpOrder(\$previous_look_up_order);\n") + ; + } + + if (defined('DEBUG')) + { + $compiler + ->outdent() + ->write("}\n\n") + ; + } + } + } +} diff --git a/phpBB/phpbb/template/twig/node/expression/binary/equalequal.php b/phpBB/phpbb/template/twig/node/expression/binary/equalequal.php new file mode 100644 index 0000000000..8ec2069114 --- /dev/null +++ b/phpBB/phpbb/template/twig/node/expression/binary/equalequal.php @@ -0,0 +1,25 @@ +raw('==='); + } +} diff --git a/phpBB/phpbb/template/twig/node/expression/binary/notequalequal.php b/phpBB/phpbb/template/twig/node/expression/binary/notequalequal.php new file mode 100644 index 0000000000..96f32c502e --- /dev/null +++ b/phpBB/phpbb/template/twig/node/expression/binary/notequalequal.php @@ -0,0 +1,25 @@ +raw('!=='); + } +} diff --git a/phpBB/phpbb/template/twig/node/include.php b/phpBB/phpbb/template/twig/node/include.php new file mode 100644 index 0000000000..5c6ae1bbcf --- /dev/null +++ b/phpBB/phpbb/template/twig/node/include.php @@ -0,0 +1,56 @@ +addDebugInfo($this); + + $compiler + ->write("\$location = ") + ->subcompile($this->getNode('expr')) + ->raw(";\n") + ->write("\$namespace = false;\n") + ->write("if (strpos(\$location, '@') === 0) {\n") + ->indent() + ->write("\$namespace = substr(\$location, 1, strpos(\$location, '/') - 1);\n") + ->write("\$previous_look_up_order = \$this->env->getNamespaceLookUpOrder();\n") + + // We set the namespace lookup order to be this namespace first, then the main path + ->write("\$this->env->setNamespaceLookUpOrder(array(\$namespace, '__main__'));\n") + ->outdent() + ->write("}\n") + ; + + parent::compile($compiler); + + $compiler + ->write("if (\$namespace) {\n") + ->indent() + ->write("\$this->env->setNamespaceLookUpOrder(\$previous_look_up_order);\n") + ->outdent() + ->write("}\n") + ; + } +} diff --git a/phpBB/phpbb/template/twig/node/includeasset.php b/phpBB/phpbb/template/twig/node/includeasset.php new file mode 100644 index 0000000000..990b1c984f --- /dev/null +++ b/phpBB/phpbb/template/twig/node/includeasset.php @@ -0,0 +1,60 @@ +environment = $environment; + + parent::__construct(array('expr' => $expr), array(), $lineno, $tag); + } + /** + * Compiles the node to PHP. + * + * @param Twig_Compiler A Twig_Compiler instance + */ + public function compile(Twig_Compiler $compiler) + { + $compiler->addDebugInfo($this); + + $config = $this->environment->get_phpbb_config(); + + $compiler + ->write("\$asset_file = ") + ->subcompile($this->getNode('expr')) + ->raw(";\n") + ->write("\$asset = new phpbb_template_asset(\$asset_file);\n") + ->write("if (substr(\$asset_file, 0, 2) !== './' && \$asset->is_relative()) {\n") + ->indent() + ->write("\$asset_path = \$asset->get_path();") + ->write("\$local_file = \$this->getEnvironment()->get_phpbb_root_path() . \$asset_path;\n") + ->write("if (!file_exists(\$local_file)) {\n") + ->indent() + ->write("\$local_file = \$this->getEnvironment()->getLoader()->getCacheKey(\$asset_path);\n") + ->write("\$asset->set_path(\$local_file, true);\n") + ->outdent() + ->write("\$asset->add_assets_version({$config['assets_version']});\n") + ->write("\$asset_file = \$asset->get_url();\n") + ->write("}\n") + ->outdent() + ->write("}\n") + ->write("\$context['definition']->append('{$this->get_definition_name()}', '") + ; + + $this->append_asset($compiler); + + $compiler + ->raw("\n');\n") + ; + } +} diff --git a/phpBB/phpbb/template/twig/node/includecss.php b/phpBB/phpbb/template/twig/node/includecss.php new file mode 100644 index 0000000000..01fda44aad --- /dev/null +++ b/phpBB/phpbb/template/twig/node/includecss.php @@ -0,0 +1,30 @@ +raw("raw("\$asset_file . '\"") + ->raw(' rel="stylesheet" type="text/css" media="screen, projection" />') + ; + } +} diff --git a/phpBB/phpbb/template/twig/node/includejs.php b/phpBB/phpbb/template/twig/node/includejs.php new file mode 100644 index 0000000000..fdf2bea3ed --- /dev/null +++ b/phpBB/phpbb/template/twig/node/includejs.php @@ -0,0 +1,32 @@ +environment->get_phpbb_config(); + + $compiler + ->raw("\n") + ; + } +} diff --git a/phpBB/phpbb/template/twig/node/includephp.php b/phpBB/phpbb/template/twig/node/includephp.php new file mode 100644 index 0000000000..dbe54f0e1a --- /dev/null +++ b/phpBB/phpbb/template/twig/node/includephp.php @@ -0,0 +1,91 @@ +environment = $environment; + + parent::__construct(array('expr' => $expr), array('ignore_missing' => (Boolean) $ignoreMissing), $lineno, $tag); + } + + /** + * Compiles the node to PHP. + * + * @param Twig_Compiler A Twig_Compiler instance + */ + public function compile(Twig_Compiler $compiler) + { + $compiler->addDebugInfo($this); + + $config = $this->environment->get_phpbb_config(); + + if (!$config['tpl_allow_php']) + { + $compiler + ->write("// INCLUDEPHP Disabled\n") + ; + + return; + } + + if ($this->getAttribute('ignore_missing')) { + $compiler + ->write("try {\n") + ->indent() + ; + } + + $compiler + ->write("\$location = ") + ->subcompile($this->getNode('expr')) + ->raw(";\n") + ->write("if (phpbb_is_absolute(\$location)) {\n") + ->indent() + // Absolute path specified + ->write("require(\$location);\n") + ->outdent() + ->write("} else if (file_exists(\$this->getEnvironment()->get_phpbb_root_path() . \$location)) {\n") + ->indent() + // PHP file relative to phpbb_root_path + ->write("require(\$this->getEnvironment()->get_phpbb_root_path() . \$location);\n") + ->outdent() + ->write("} else {\n") + ->indent() + // Local path (behaves like INCLUDE) + ->write("require(\$this->getEnvironment()->getLoader()->getCacheKey(\$location));\n") + ->outdent() + ->write("}\n") + ; + + if ($this->getAttribute('ignore_missing')) { + $compiler + ->outdent() + ->write("} catch (Twig_Error_Loader \$e) {\n") + ->indent() + ->write("// ignore missing template\n") + ->outdent() + ->write("}\n\n") + ; + } + } +} diff --git a/phpBB/phpbb/template/twig/node/php.php b/phpBB/phpbb/template/twig/node/php.php new file mode 100644 index 0000000000..c11539ea7f --- /dev/null +++ b/phpBB/phpbb/template/twig/node/php.php @@ -0,0 +1,55 @@ +environment = $environment; + + parent::__construct(array('text' => $text), array(), $lineno, $tag); + } + + /** + * Compiles the node to PHP. + * + * @param Twig_Compiler A Twig_Compiler instance + */ + public function compile(Twig_Compiler $compiler) + { + $compiler->addDebugInfo($this); + + $config = $this->environment->get_phpbb_config(); + + if (!$config['tpl_allow_php']) + { + $compiler + ->write("// PHP Disabled\n") + ; + + return; + } + + $compiler + ->raw($this->getNode('text')->getAttribute('data')) + ; + } +} diff --git a/phpBB/phpbb/template/twig/tokenparser/define.php b/phpBB/phpbb/template/twig/tokenparser/define.php new file mode 100644 index 0000000000..4ea15388c4 --- /dev/null +++ b/phpBB/phpbb/template/twig/tokenparser/define.php @@ -0,0 +1,66 @@ +getLine(); + $stream = $this->parser->getStream(); + $name = $this->parser->getExpressionParser()->parseExpression(); + + $capture = false; + if ($stream->test(Twig_Token::OPERATOR_TYPE, '=')) { + $stream->next(); + $value = $this->parser->getExpressionParser()->parseExpression(); + + $stream->expect(Twig_Token::BLOCK_END_TYPE); + } else { + $capture = true; + + $stream->expect(Twig_Token::BLOCK_END_TYPE); + + $value = $this->parser->subparse(array($this, 'decideBlockEnd'), true); + $stream->expect(Twig_Token::BLOCK_END_TYPE); + } + + return new phpbb_template_twig_node_define($capture, $name, $value, $lineno, $this->getTag()); + } + + public function decideBlockEnd(Twig_Token $token) + { + return $token->test('ENDDEFINE'); + } + + /** + * Gets the tag name associated with this token parser. + * + * @return string The tag name + */ + public function getTag() + { + return 'DEFINE'; + } +} diff --git a/phpBB/phpbb/template/twig/tokenparser/event.php b/phpBB/phpbb/template/twig/tokenparser/event.php new file mode 100644 index 0000000000..e4dddd6dcc --- /dev/null +++ b/phpBB/phpbb/template/twig/tokenparser/event.php @@ -0,0 +1,47 @@ +parser->getExpressionParser()->parseExpression(); + + $stream = $this->parser->getStream(); + $stream->expect(Twig_Token::BLOCK_END_TYPE); + + return new phpbb_template_twig_node_event($expr, $this->parser->getEnvironment(), $token->getLine(), $this->getTag()); + } + + /** + * Gets the tag name associated with this token parser. + * + * @return string The tag name + */ + public function getTag() + { + return 'EVENT'; + } +} diff --git a/phpBB/phpbb/template/twig/tokenparser/include.php b/phpBB/phpbb/template/twig/tokenparser/include.php new file mode 100644 index 0000000000..520f9fd1a0 --- /dev/null +++ b/phpBB/phpbb/template/twig/tokenparser/include.php @@ -0,0 +1,46 @@ +parser->getExpressionParser()->parseExpression(); + + list($variables, $only, $ignoreMissing) = $this->parseArguments(); + + return new phpbb_template_twig_node_include($expr, $variables, $only, $ignoreMissing, $token->getLine(), $this->getTag()); + } + + /** + * Gets the tag name associated with this token parser. + * + * @return string The tag name + */ + public function getTag() + { + return 'INCLUDE'; + } +} diff --git a/phpBB/phpbb/template/twig/tokenparser/includecss.php b/phpBB/phpbb/template/twig/tokenparser/includecss.php new file mode 100644 index 0000000000..6c24dda647 --- /dev/null +++ b/phpBB/phpbb/template/twig/tokenparser/includecss.php @@ -0,0 +1,38 @@ +parser->getExpressionParser()->parseExpression(); + + $stream = $this->parser->getStream(); + $stream->expect(Twig_Token::BLOCK_END_TYPE); + + return new phpbb_template_twig_node_includecss($expr, $this->parser->getEnvironment(), $token->getLine(), $this->getTag()); + } + + /** + * Gets the tag name associated with this token parser. + * + * @return string The tag name + */ + public function getTag() + { + return 'INCLUDECSS'; + } +} diff --git a/phpBB/phpbb/template/twig/tokenparser/includejs.php b/phpBB/phpbb/template/twig/tokenparser/includejs.php new file mode 100644 index 0000000000..b02b2f89ba --- /dev/null +++ b/phpBB/phpbb/template/twig/tokenparser/includejs.php @@ -0,0 +1,47 @@ +parser->getExpressionParser()->parseExpression(); + + $stream = $this->parser->getStream(); + $stream->expect(Twig_Token::BLOCK_END_TYPE); + + return new phpbb_template_twig_node_includejs($expr, $this->parser->getEnvironment(), $token->getLine(), $this->getTag()); + } + + /** + * Gets the tag name associated with this token parser. + * + * @return string The tag name + */ + public function getTag() + { + return 'INCLUDEJS'; + } +} diff --git a/phpBB/phpbb/template/twig/tokenparser/includephp.php b/phpBB/phpbb/template/twig/tokenparser/includephp.php new file mode 100644 index 0000000000..13fe6de8a6 --- /dev/null +++ b/phpBB/phpbb/template/twig/tokenparser/includephp.php @@ -0,0 +1,56 @@ +parser->getExpressionParser()->parseExpression(); + + $stream = $this->parser->getStream(); + + $ignoreMissing = false; + if ($stream->test(Twig_Token::NAME_TYPE, 'ignore')) { + $stream->next(); + $stream->expect(Twig_Token::NAME_TYPE, 'missing'); + + $ignoreMissing = true; + } + + $stream->expect(Twig_Token::BLOCK_END_TYPE); + + return new phpbb_template_twig_node_includephp($expr, $this->parser->getEnvironment(), $ignoreMissing, $token->getLine(), $this->getTag()); + } + + /** + * Gets the tag name associated with this token parser. + * + * @return string The tag name + */ + public function getTag() + { + return 'INCLUDEPHP'; + } +} diff --git a/phpBB/phpbb/template/twig/tokenparser/php.php b/phpBB/phpbb/template/twig/tokenparser/php.php new file mode 100644 index 0000000000..197980a59a --- /dev/null +++ b/phpBB/phpbb/template/twig/tokenparser/php.php @@ -0,0 +1,55 @@ +parser->getStream(); + + $stream->expect(Twig_Token::BLOCK_END_TYPE); + + $body = $this->parser->subparse(array($this, 'decideEnd'), true); + + $stream->expect(Twig_Token::BLOCK_END_TYPE); + + return new phpbb_template_twig_node_php($body, $this->parser->getEnvironment(), $token->getLine(), $this->getTag()); + } + + public function decideEnd(Twig_Token $token) + { + return $token->test('ENDPHP'); + } + + /** + * Gets the tag name associated with this token parser. + * + * @return string The tag name + */ + public function getTag() + { + return 'PHP'; + } +} diff --git a/phpBB/phpbb/template/twig/twig.php b/phpBB/phpbb/template/twig/twig.php new file mode 100644 index 0000000000..92a37d1634 --- /dev/null +++ b/phpBB/phpbb/template/twig/twig.php @@ -0,0 +1,465 @@ +phpbb_root_path = $phpbb_root_path; + $this->adm_relative_path = $adm_relative_path; + $this->php_ext = $php_ext; + $this->config = $config; + $this->user = $user; + $this->context = $context; + $this->extension_manager = $extension_manager; + + $this->cachepath = $phpbb_root_path . 'cache/twig/'; + + // Initiate the loader, __main__ namespace paths will be setup later in set_style_names() + $loader = new Twig_Loader_Filesystem(''); + + $this->twig = new phpbb_template_twig_environment( + $this->config, + ($this->extension_manager) ? $this->extension_manager->all_enabled() : array(), + $this->phpbb_root_path, + $loader, + array( + 'cache' => (defined('IN_INSTALL')) ? false : $this->cachepath, + 'debug' => defined('DEBUG'), + 'auto_reload' => (bool) $this->config['load_tplcompile'], + 'autoescape' => false, + ) + ); + + $this->twig->addExtension( + new phpbb_template_twig_extension( + $this->context, + $this->user + ) + ); + + $lexer = new phpbb_template_twig_lexer($this->twig); + + $this->twig->setLexer($lexer); + } + + /** + * Clear the cache + * + * @return phpbb_template + */ + public function clear_cache() + { + if (is_dir($this->cachepath)) + { + $this->twig->clearCacheFiles(); + } + + return $this; + } + + /** + * Sets the template filenames for handles. + * + * @param array $filename_array Should be a hash of handle => filename pairs. + * @return phpbb_template $this + */ + public function set_filenames(array $filename_array) + { + $this->filenames = array_merge($this->filenames, $filename_array); + + return $this; + } + + /** + * Sets the style names/paths corresponding to style hierarchy being compiled + * and/or rendered. + * + * @param array $style_names List of style names in inheritance tree order + * @param array $style_paths List of style paths in inheritance tree order + * @param bool $is_core True if the style names are the "core" styles for this page load + * Core means the main phpBB template files + * @return phpbb_template $this + */ + public function set_style_names(array $style_names, array $style_paths, $is_core = false) + { + $this->style_names = $style_names; + + // Set as __main__ namespace + $this->twig->getLoader()->setPaths($style_paths); + + // Core style namespace from phpbb_style::set_style() + if ($is_core) + { + $this->twig->getLoader()->setPaths($style_paths, 'core'); + } + + // Add admin namespace + if (is_dir($this->phpbb_root_path . $this->adm_relative_path . 'style/')) + { + $this->twig->getLoader()->setPaths($this->phpbb_root_path . $this->adm_relative_path . 'style/', 'admin'); + } + + // Add all namespaces for all extensions + if ($this->extension_manager instanceof phpbb_extension_manager) + { + $style_names[] = 'all'; + + foreach ($this->extension_manager->all_enabled() as $ext_namespace => $ext_path) + { + // namespaces cannot contain / + $namespace = str_replace('/', '_', $ext_namespace); + $paths = array(); + + foreach ($style_names as $style_name) + { + $ext_style_path = $ext_path . 'styles/' . $style_name . '/template'; + + if (is_dir($ext_style_path)) + { + $paths[] = $ext_style_path; + } + } + + $this->twig->getLoader()->setPaths($paths, $namespace); + } + } + + return $this; + } + + /** + * Clears all variables and blocks assigned to this template. + * + * @return phpbb_template $this + */ + public function destroy() + { + $this->context = array(); + + return $this; + } + + /** + * Reset/empty complete block + * + * @param string $blockname Name of block to destroy + * @return phpbb_template $this + */ + public function destroy_block_vars($blockname) + { + $this->context->destroy_block_vars($blockname); + + return $this; + } + + /** + * Display a template for provided handle. + * + * The template will be loaded and compiled, if necessary, first. + * + * This function calls hooks. + * + * @param string $handle Handle to display + * @return phpbb_template $this + */ + public function display($handle) + { + $result = $this->call_hook($handle, __FUNCTION__); + if ($result !== false) + { + return $result[0]; + } + + $this->twig->display($this->get_filename_from_handle($handle), $this->get_template_vars()); + + return $this; + } + + /** + * Calls hook if any is defined. + * + * @param string $handle Template handle being displayed. + * @param string $method Method name of the caller. + */ + protected function call_hook($handle, $method) + { + global $phpbb_hook; + + if (!empty($phpbb_hook) && $phpbb_hook->call_hook(array(__CLASS__, $method), $handle, $this)) + { + if ($phpbb_hook->hook_return(array(__CLASS__, $method))) + { + $result = $phpbb_hook->hook_return_result(array(__CLASS__, $method)); + return array($result); + } + } + + return false; + } + + /** + * Display the handle and assign the output to a template variable + * or return the compiled result. + * + * @param string $handle Handle to operate on + * @param string $template_var Template variable to assign compiled handle to + * @param bool $return_content If true return compiled handle, otherwise assign to $template_var + * @return phpbb_template|string if $return_content is true return string of the compiled handle, otherwise return $this + */ + public function assign_display($handle, $template_var = '', $return_content = true) + { + if ($return_content) + { + return $this->twig->render($this->get_filename_from_handle($handle), $this->get_template_vars()); + } + + $this->assign_var($template_var, $this->twig->render($this->get_filename_from_handle($handle, $this->get_template_vars()))); + + return $this; + } + + /** + * Assign key variable pairs from an array + * + * @param array $vararray A hash of variable name => value pairs + * @return phpbb_template $this + */ + public function assign_vars(array $vararray) + { + foreach ($vararray as $key => $val) + { + $this->assign_var($key, $val); + } + + return $this; + } + + /** + * Assign a single scalar value to a single key. + * + * Value can be a string, an integer or a boolean. + * + * @param string $varname Variable name + * @param string $varval Value to assign to variable + * @return phpbb_template $this + */ + public function assign_var($varname, $varval) + { + $this->context->assign_var($varname, $varval); + + return $this; + } + + /** + * Append text to the string value stored in a key. + * + * Text is appended using the string concatenation operator (.). + * + * @param string $varname Variable name + * @param string $varval Value to append to variable + * @return phpbb_template $this + */ + public function append_var($varname, $varval) + { + $this->context->append_var($varname, $varval); + + return $this; + } + + /** + * Assign key variable pairs from an array to a specified block + * @param string $blockname Name of block to assign $vararray to + * @param array $vararray A hash of variable name => value pairs + * @return phpbb_template $this + */ + public function assign_block_vars($blockname, array $vararray) + { + $this->context->assign_block_vars($blockname, $vararray); + + return $this; + } + + /** + * Change already assigned key variable pair (one-dimensional - single loop entry) + * + * An example of how to use this function: + * {@example alter_block_array.php} + * + * @param string $blockname the blockname, for example 'loop' + * @param array $vararray the var array to insert/add or merge + * @param mixed $key Key to search for + * + * array: KEY => VALUE [the key/value pair to search for within the loop to determine the correct position] + * + * int: Position [the position to change or insert at directly given] + * + * If key is false the position is set to 0 + * If key is true the position is set to the last entry + * + * @param string $mode Mode to execute (valid modes are 'insert' and 'change') + * + * If insert, the vararray is inserted at the given position (position counting from zero). + * If change, the current block gets merged with the vararray (resulting in new key/value pairs be added and existing keys be replaced by the new value). + * + * Since counting begins by zero, inserting at the last position will result in this array: array(vararray, last positioned array) + * and inserting at position 1 will result in this array: array(first positioned array, vararray, following vars) + * + * @return bool false on error, true on success + */ + public function alter_block_array($blockname, array $vararray, $key = false, $mode = 'insert') + { + return $this->context->alter_block_array($blockname, $vararray, $key, $mode); + } + + /** + * Get template vars in a format Twig will use (from the context) + * + * @return array + */ + public function get_template_vars() + { + $context_vars = $this->context->get_data_ref(); + + $vars = array_merge( + $context_vars['.'][0], // To get normal vars + $context_vars, // To get loops + array( + 'definition' => new phpbb_template_twig_definition(), + 'user' => $this->user, + ) + ); + + // cleanup + unset($vars['.']); + + return $vars; + } + + /** + * Get a filename from the handle + * + * @param string $handle + * @return string + */ + protected function get_filename_from_handle($handle) + { + return (isset($this->filenames[$handle])) ? $this->filenames[$handle] : $handle; + } + + /** + * Get path to template for handle (required for BBCode parser) + * + * @return string + */ + public function get_source_file_for_handle($handle) + { + return $this->twig->getLoader()->getCacheKey($this->get_filename_from_handle($handle)); + } +} diff --git a/phpBB/phpbb/tree/interface.php b/phpBB/phpbb/tree/interface.php new file mode 100644 index 0000000000..cc8aab2115 --- /dev/null +++ b/phpBB/phpbb/tree/interface.php @@ -0,0 +1,122 @@ + down, > 0 => up + * @return bool True if the item was moved + */ + public function move($item_id, $delta); + + /** + * Move an item down by 1 + * + * @param int $item_id The item to be moved + * @return bool True if the item was moved + */ + public function move_down($item_id); + + /** + * Move an item up by 1 + * + * @param int $item_id The item to be moved + * @return bool True if the item was moved + */ + public function move_up($item_id); + + /** + * Moves all children of one item to another item + * + * If the new parent already has children, the new children are appended + * to the list. + * + * @param int $current_parent_id The current parent item + * @param int $new_parent_id The new parent item + * @return bool True if any items where moved + */ + public function move_children($current_parent_id, $new_parent_id); + + /** + * Change parent item + * + * Moves the item to the bottom of the new parent's list of children + * + * @param int $item_id The item to be moved + * @param int $new_parent_id The new parent item + * @return bool True if the parent was set successfully + */ + public function change_parent($item_id, $new_parent_id); + + /** + * Get all items that are either ancestors or descendants of the item + * + * @param int $item_id Id of the item to retrieve the ancestors/descendants from + * @param bool $order_asc Order the items ascendingly (most outer ancestor first) + * @param bool $include_item Should the item matching the given item id be included in the list as well + * @return array Array of items (containing all columns from the item table) + * ID => Item data + */ + public function get_path_and_subtree_data($item_id, $order_asc, $include_item); + + /** + * Get all of the item's ancestors + * + * @param int $item_id Id of the item to retrieve the ancestors from + * @param bool $order_asc Order the items ascendingly (most outer ancestor first) + * @param bool $include_item Should the item matching the given item id be included in the list as well + * @return array Array of items (containing all columns from the item table) + * ID => Item data + */ + public function get_path_data($item_id, $order_asc, $include_item); + + /** + * Get all of the item's descendants + * + * @param int $item_id Id of the item to retrieve the descendants from + * @param bool $order_asc Order the items ascendingly + * @param bool $include_item Should the item matching the given item id be included in the list as well + * @return array Array of items (containing all columns from the item table) + * ID => Item data + */ + public function get_subtree_data($item_id, $order_asc, $include_item); +} diff --git a/phpBB/phpbb/tree/nestedset.php b/phpBB/phpbb/tree/nestedset.php new file mode 100644 index 0000000000..4d851a87a8 --- /dev/null +++ b/phpBB/phpbb/tree/nestedset.php @@ -0,0 +1,850 @@ +db = $db; + $this->lock = $lock; + + $this->table_name = $table_name; + $this->message_prefix = $message_prefix; + $this->sql_where = $sql_where; + $this->item_basic_data = (!empty($item_basic_data)) ? $item_basic_data : array('*'); + + if (!empty($columns)) + { + foreach ($columns as $column => $name) + { + $column_name = 'column_' . $column; + $this->$column_name = $name; + } + } + } + + /** + * Returns additional sql where restrictions + * + * @param string $operator SQL operator that needs to be prepended to sql_where, + * if it is not empty. + * @param string $column_prefix Prefix that needs to be prepended to column names + * @return string Returns additional where statements to narrow down the tree, + * prefixed with operator and prepended column_prefix to column names + */ + public function get_sql_where($operator = 'AND', $column_prefix = '') + { + return (!$this->sql_where) ? '' : $operator . ' ' . sprintf($this->sql_where, $column_prefix); + } + + /** + * Acquires a lock on the item table + * + * @return bool True if the lock was acquired, false if it has been acquired previously + * + * @throws RuntimeException If the lock could not be acquired + */ + protected function acquire_lock() + { + if ($this->lock->owns_lock()) + { + return false; + } + + if (!$this->lock->acquire()) + { + throw new RuntimeException($this->message_prefix . 'LOCK_FAILED_ACQUIRE'); + } + + return true; + } + + /** + * @inheritdoc + */ + public function insert(array $additional_data) + { + $item_data = $this->reset_nestedset_values($additional_data); + + $sql = 'INSERT INTO ' . $this->table_name . ' ' . $this->db->sql_build_array('INSERT', $item_data); + $this->db->sql_query($sql); + + $item_data[$this->column_item_id] = (int) $this->db->sql_nextid(); + + return array_merge($item_data, $this->add_item_to_nestedset($item_data[$this->column_item_id])); + } + + /** + * Add an item which already has a database row at the end of the tree + * + * @param int $item_id The item to be added + * @return array Array with updated data, if the item was added successfully + * Empty array otherwise + */ + protected function add_item_to_nestedset($item_id) + { + $sql = 'SELECT MAX(' . $this->column_right_id . ') AS ' . $this->column_right_id . ' + FROM ' . $this->table_name . ' + ' . $this->get_sql_where('WHERE'); + $result = $this->db->sql_query($sql); + $current_max_right_id = (int) $this->db->sql_fetchfield($this->column_right_id); + $this->db->sql_freeresult($result); + + $update_item_data = array( + $this->column_parent_id => 0, + $this->column_left_id => $current_max_right_id + 1, + $this->column_right_id => $current_max_right_id + 2, + $this->column_item_parents => '', + ); + + $sql = 'UPDATE ' . $this->table_name . ' + SET ' . $this->db->sql_build_array('UPDATE', $update_item_data) . ' + WHERE ' . $this->column_item_id . ' = ' . (int) $item_id . ' + AND ' . $this->column_parent_id . ' = 0 + AND ' . $this->column_left_id . ' = 0 + AND ' . $this->column_right_id . ' = 0'; + $this->db->sql_query($sql); + + return ($this->db->sql_affectedrows() == 1) ? $update_item_data : array(); + } + + /** + * Remove an item from the tree without deleting it from the database + * + * Also removes all subitems from the tree without deleting them from the database either + * + * @param int $item_id The item to be deleted + * @return array Item ids that have been removed + */ + protected function remove_item_from_nestedset($item_id) + { + $item_id = (int) $item_id; + if (!$item_id) + { + throw new OutOfBoundsException($this->message_prefix . 'INVALID_ITEM'); + } + + $items = $this->get_subtree_data($item_id); + $item_ids = array_keys($items); + + if (empty($items) || !isset($items[$item_id])) + { + throw new OutOfBoundsException($this->message_prefix . 'INVALID_ITEM'); + } + + $this->remove_subset($item_ids, $items[$item_id]); + + return $item_ids; + } + + /** + * @inheritdoc + */ + public function delete($item_id) + { + $removed_items = $this->remove_item_from_nestedset($item_id); + + $sql = 'DELETE FROM ' . $this->table_name . ' + WHERE ' . $this->db->sql_in_set($this->column_item_id, $removed_items) . ' + ' . $this->get_sql_where('AND'); + $this->db->sql_query($sql); + + return $removed_items; + } + + /** + * @inheritdoc + */ + public function move($item_id, $delta) + { + if ($delta == 0) + { + return false; + } + + $this->acquire_lock(); + + $action = ($delta > 0) ? 'move_up' : 'move_down'; + $delta = abs($delta); + + // Keep $this->get_sql_where() here, to ensure we are in the right tree. + $sql = 'SELECT * + FROM ' . $this->table_name . ' + WHERE ' . $this->column_item_id . ' = ' . (int) $item_id . ' + ' . $this->get_sql_where(); + $result = $this->db->sql_query_limit($sql, $delta); + $item = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if (!$item) + { + $this->lock->release(); + throw new OutOfBoundsException($this->message_prefix . 'INVALID_ITEM'); + } + + /** + * Fetch all the siblings between the item's current spot + * and where we want to move it to. If there are less than $delta + * siblings between the current spot and the target then the + * item will move as far as possible + */ + $sql = "SELECT {$this->column_item_id}, {$this->column_parent_id}, {$this->column_left_id}, {$this->column_right_id}, {$this->column_item_parents} + FROM " . $this->table_name . ' + WHERE ' . $this->column_parent_id . ' = ' . (int) $item[$this->column_parent_id] . ' + ' . $this->get_sql_where() . ' + AND '; + + if ($action == 'move_up') + { + $sql .= $this->column_right_id . ' < ' . (int) $item[$this->column_right_id] . ' ORDER BY ' . $this->column_right_id . ' DESC'; + } + else + { + $sql .= $this->column_left_id . ' > ' . (int) $item[$this->column_left_id] . ' ORDER BY ' . $this->column_left_id . ' ASC'; + } + + $result = $this->db->sql_query_limit($sql, $delta); + + $target = false; + while ($row = $this->db->sql_fetchrow($result)) + { + $target = $row; + } + $this->db->sql_freeresult($result); + + if (!$target) + { + $this->lock->release(); + // The item is already on top or bottom + return false; + } + + /** + * $left_id and $right_id define the scope of the items that are affected by the move. + * $diff_up and $diff_down are the values to substract or add to each item's left_id + * and right_id in order to move them up or down. + * $move_up_left and $move_up_right define the scope of the items that are moving + * up. Other items in the scope of ($left_id, $right_id) are considered to move down. + */ + if ($action == 'move_up') + { + $left_id = (int) $target[$this->column_left_id]; + $right_id = (int) $item[$this->column_right_id]; + + $diff_up = (int) $item[$this->column_left_id] - (int) $target[$this->column_left_id]; + $diff_down = (int) $item[$this->column_right_id] + 1 - (int) $item[$this->column_left_id]; + + $move_up_left = (int) $item[$this->column_left_id]; + $move_up_right = (int) $item[$this->column_right_id]; + } + else + { + $left_id = (int) $item[$this->column_left_id]; + $right_id = (int) $target[$this->column_right_id]; + + $diff_up = (int) $item[$this->column_right_id] + 1 - (int) $item[$this->column_left_id]; + $diff_down = (int) $target[$this->column_right_id] - (int) $item[$this->column_right_id]; + + $move_up_left = (int) $item[$this->column_right_id] + 1; + $move_up_right = (int) $target[$this->column_right_id]; + } + + // Now do the dirty job + $sql = 'UPDATE ' . $this->table_name . ' + SET ' . $this->column_left_id . ' = ' . $this->column_left_id . ' + CASE + WHEN ' . $this->column_left_id . " BETWEEN {$move_up_left} AND {$move_up_right} THEN -{$diff_up} + ELSE {$diff_down} + END, + " . $this->column_right_id . ' = ' . $this->column_right_id . ' + CASE + WHEN ' . $this->column_right_id . " BETWEEN {$move_up_left} AND {$move_up_right} THEN -{$diff_up} + ELSE {$diff_down} + END + WHERE + " . $this->column_left_id . " BETWEEN {$left_id} AND {$right_id} + AND " . $this->column_right_id . " BETWEEN {$left_id} AND {$right_id} + " . $this->get_sql_where(); + $this->db->sql_query($sql); + + $this->lock->release(); + + return true; + } + + /** + * @inheritdoc + */ + public function move_down($item_id) + { + return $this->move($item_id, -1); + } + + /** + * @inheritdoc + */ + public function move_up($item_id) + { + return $this->move($item_id, 1); + } + + /** + * @inheritdoc + */ + public function move_children($current_parent_id, $new_parent_id) + { + $current_parent_id = (int) $current_parent_id; + $new_parent_id = (int) $new_parent_id; + + if ($current_parent_id == $new_parent_id) + { + return false; + } + + if (!$current_parent_id) + { + throw new OutOfBoundsException($this->message_prefix . 'INVALID_ITEM'); + } + + $this->acquire_lock(); + + $item_data = $this->get_subtree_data($current_parent_id); + if (!isset($item_data[$current_parent_id])) + { + $this->lock->release(); + throw new OutOfBoundsException($this->message_prefix . 'INVALID_ITEM'); + } + + $current_parent = $item_data[$current_parent_id]; + unset($item_data[$current_parent_id]); + $move_items = array_keys($item_data); + + if (($current_parent[$this->column_right_id] - $current_parent[$this->column_left_id]) <= 1) + { + $this->lock->release(); + return false; + } + + if (in_array($new_parent_id, $move_items)) + { + $this->lock->release(); + throw new OutOfBoundsException($this->message_prefix . 'INVALID_PARENT'); + } + + $diff = sizeof($move_items) * 2; + $sql_exclude_moved_items = $this->db->sql_in_set($this->column_item_id, $move_items, true); + + $this->db->sql_transaction('begin'); + + $this->remove_subset($move_items, $current_parent, false, true); + + if ($new_parent_id) + { + // Retrieve new-parent again, it may have been changed... + $sql = 'SELECT * + FROM ' . $this->table_name . ' + WHERE ' . $this->column_item_id . ' = ' . $new_parent_id; + $result = $this->db->sql_query($sql); + $new_parent = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if (!$new_parent) + { + $this->db->sql_transaction('rollback'); + $this->lock->release(); + throw new OutOfBoundsException($this->message_prefix . 'INVALID_PARENT'); + } + + $new_right_id = $this->prepare_adding_subset($move_items, $new_parent, true); + + if ($new_right_id > $current_parent[$this->column_right_id]) + { + $diff = ' + ' . ($new_right_id - $current_parent[$this->column_right_id]); + } + else + { + $diff = ' - ' . abs($new_right_id - $current_parent[$this->column_right_id]); + } + } + else + { + $sql = 'SELECT MAX(' . $this->column_right_id . ') AS ' . $this->column_right_id . ' + FROM ' . $this->table_name . ' + WHERE ' . $sql_exclude_moved_items . ' + ' . $this->get_sql_where('AND'); + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + $diff = ' + ' . ($row[$this->column_right_id] - $current_parent[$this->column_left_id]); + } + + $sql = 'UPDATE ' . $this->table_name . ' + SET ' . $this->column_left_id . ' = ' . $this->column_left_id . $diff . ', + ' . $this->column_right_id . ' = ' . $this->column_right_id . $diff . ', + ' . $this->column_parent_id . ' = ' . $this->db->sql_case($this->column_parent_id . ' = ' . $current_parent_id, $new_parent_id, $this->column_parent_id) . ', + ' . $this->column_item_parents . " = '' + WHERE " . $this->db->sql_in_set($this->column_item_id, $move_items) . ' + ' . $this->get_sql_where('AND'); + $this->db->sql_query($sql); + + $this->db->sql_transaction('commit'); + $this->lock->release(); + + return true; + } + + /** + * @inheritdoc + */ + public function change_parent($item_id, $new_parent_id) + { + $item_id = (int) $item_id; + $new_parent_id = (int) $new_parent_id; + + if ($item_id == $new_parent_id) + { + return false; + } + + if (!$item_id) + { + throw new OutOfBoundsException($this->message_prefix . 'INVALID_ITEM'); + } + + $this->acquire_lock(); + + $item_data = $this->get_subtree_data($item_id); + if (!isset($item_data[$item_id])) + { + $this->lock->release(); + throw new OutOfBoundsException($this->message_prefix . 'INVALID_ITEM'); + } + + $item = $item_data[$item_id]; + $move_items = array_keys($item_data); + + if (in_array($new_parent_id, $move_items)) + { + $this->lock->release(); + throw new OutOfBoundsException($this->message_prefix . 'INVALID_PARENT'); + } + + $diff = sizeof($move_items) * 2; + $sql_exclude_moved_items = $this->db->sql_in_set($this->column_item_id, $move_items, true); + + $this->db->sql_transaction('begin'); + + $this->remove_subset($move_items, $item, false, true); + + if ($new_parent_id) + { + // Retrieve new-parent again, it may have been changed... + $sql = 'SELECT * + FROM ' . $this->table_name . ' + WHERE ' . $this->column_item_id . ' = ' . $new_parent_id; + $result = $this->db->sql_query($sql); + $new_parent = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if (!$new_parent) + { + $this->db->sql_transaction('rollback'); + $this->lock->release(); + throw new OutOfBoundsException($this->message_prefix . 'INVALID_PARENT'); + } + + $new_right_id = $this->prepare_adding_subset($move_items, $new_parent, true); + + if ($new_right_id > (int) $item[$this->column_right_id]) + { + $diff = ' + ' . ($new_right_id - (int) $item[$this->column_right_id] - 1); + } + else + { + $diff = ' - ' . abs($new_right_id - (int) $item[$this->column_right_id] - 1); + } + } + else + { + $sql = 'SELECT MAX(' . $this->column_right_id . ') AS ' . $this->column_right_id . ' + FROM ' . $this->table_name . ' + WHERE ' . $sql_exclude_moved_items . ' + ' . $this->get_sql_where('AND'); + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + $diff = ' + ' . ($row[$this->column_right_id] - (int) $item[$this->column_left_id] + 1); + } + + $sql = 'UPDATE ' . $this->table_name . ' + SET ' . $this->column_left_id . ' = ' . $this->column_left_id . $diff . ', + ' . $this->column_right_id . ' = ' . $this->column_right_id . $diff . ', + ' . $this->column_parent_id . ' = ' . $this->db->sql_case($this->column_item_id . ' = ' . $item_id, $new_parent_id, $this->column_parent_id) . ', + ' . $this->column_item_parents . " = '' + WHERE " . $this->db->sql_in_set($this->column_item_id, $move_items) . ' + ' . $this->get_sql_where('AND'); + $this->db->sql_query($sql); + + $this->db->sql_transaction('commit'); + $this->lock->release(); + + return true; + } + + /** + * @inheritdoc + */ + public function get_path_and_subtree_data($item_id, $order_asc = true, $include_item = true) + { + $condition = 'i2.' . $this->column_left_id . ' BETWEEN i1.' . $this->column_left_id . ' AND i1.' . $this->column_right_id . ' + OR i1.' . $this->column_left_id . ' BETWEEN i2.' . $this->column_left_id . ' AND i2.' . $this->column_right_id; + + return $this->get_set_of_nodes_data($item_id, $condition, $order_asc, $include_item); + } + + /** + * @inheritdoc + */ + public function get_path_data($item_id, $order_asc = true, $include_item = true) + { + $condition = 'i1.' . $this->column_left_id . ' BETWEEN i2.' . $this->column_left_id . ' AND i2.' . $this->column_right_id . ''; + + return $this->get_set_of_nodes_data($item_id, $condition, $order_asc, $include_item); + } + + /** + * @inheritdoc + */ + public function get_subtree_data($item_id, $order_asc = true, $include_item = true) + { + $condition = 'i2.' . $this->column_left_id . ' BETWEEN i1.' . $this->column_left_id . ' AND i1.' . $this->column_right_id . ''; + + return $this->get_set_of_nodes_data($item_id, $condition, $order_asc, $include_item); + } + + /** + * Get items that are related to the given item by the condition + * + * @param int $item_id Id of the item to retrieve the node set from + * @param string $condition Query string restricting the item list + * @param bool $order_asc Order the items ascending by their left_id + * @param bool $include_item Should the item matching the given item id be included in the list as well + * @return array Array of items (containing all columns from the item table) + * ID => Item data + */ + protected function get_set_of_nodes_data($item_id, $condition, $order_asc = true, $include_item = true) + { + $rows = array(); + + $sql = 'SELECT i2.* + FROM ' . $this->table_name . ' i1 + LEFT JOIN ' . $this->table_name . " i2 + ON (($condition) " . $this->get_sql_where('AND', 'i2.') . ') + WHERE i1.' . $this->column_item_id . ' = ' . (int) $item_id . ' + ' . $this->get_sql_where('AND', 'i1.') . ' + ORDER BY i2.' . $this->column_left_id . ' ' . ($order_asc ? 'ASC' : 'DESC'); + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + if (!$include_item && $item_id == $row[$this->column_item_id]) + { + continue; + } + + $rows[(int) $row[$this->column_item_id]] = $row; + } + $this->db->sql_freeresult($result); + + return $rows; + } + + /** + * Get basic data of all parent items + * + * Basic data is defined in the $item_basic_data property. + * Data is cached in the item_parents column in the item table + * + * @param array $item The item to get the path from + * @return array Array of items (containing basic columns from the item table) + * ID => Item data + */ + public function get_path_basic_data(array $item) + { + $parents = array(); + if ($item[$this->column_parent_id]) + { + if (!$item[$this->column_item_parents]) + { + $sql = 'SELECT ' . implode(', ', $this->item_basic_data) . ' + FROM ' . $this->table_name . ' + WHERE ' . $this->column_left_id . ' < ' . (int) $item[$this->column_left_id] . ' + AND ' . $this->column_right_id . ' > ' . (int) $item[$this->column_right_id] . ' + ' . $this->get_sql_where('AND') . ' + ORDER BY ' . $this->column_left_id . ' ASC'; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $parents[$row[$this->column_item_id]] = $row; + } + $this->db->sql_freeresult($result); + + $item_parents = serialize($parents); + + $sql = 'UPDATE ' . $this->table_name . ' + SET ' . $this->column_item_parents . " = '" . $this->db->sql_escape($item_parents) . "' + WHERE " . $this->column_parent_id . ' = ' . (int) $item[$this->column_parent_id]; + $this->db->sql_query($sql); + } + else + { + $parents = unserialize($item[$this->column_item_parents]); + } + } + + return $parents; + } + + /** + * Remove a subset from the nested set + * + * @param array $subset_items Subset of items to remove + * @param array $bounding_item Item containing the right bound of the subset + * @param bool $set_subset_zero Should the parent, left and right id of the items be set to 0, or kept unchanged? + * In case of removing an item from the tree, we should the values to 0 + * In case of moving an item, we shouldkeep the original values, in order to allow "+ diff" later + * @return null + */ + protected function remove_subset(array $subset_items, array $bounding_item, $set_subset_zero = true) + { + $acquired_new_lock = $this->acquire_lock(); + + $diff = sizeof($subset_items) * 2; + $sql_subset_items = $this->db->sql_in_set($this->column_item_id, $subset_items); + $sql_not_subset_items = $this->db->sql_in_set($this->column_item_id, $subset_items, true); + + $sql_is_parent = $this->column_left_id . ' <= ' . (int) $bounding_item[$this->column_right_id] . ' + AND ' . $this->column_right_id . ' >= ' . (int) $bounding_item[$this->column_right_id]; + + $sql_is_right = $this->column_left_id . ' > ' . (int) $bounding_item[$this->column_right_id]; + + $set_left_id = $this->db->sql_case($sql_is_right, $this->column_left_id . ' - ' . $diff, $this->column_left_id); + $set_right_id = $this->db->sql_case($sql_is_parent . ' OR ' . $sql_is_right, $this->column_right_id . ' - ' . $diff, $this->column_right_id); + + if ($set_subset_zero) + { + $set_left_id = $this->db->sql_case($sql_subset_items, 0, $set_left_id); + $set_right_id = $this->db->sql_case($sql_subset_items, 0, $set_right_id); + } + + $sql = 'UPDATE ' . $this->table_name . ' + SET ' . (($set_subset_zero) ? $this->column_parent_id . ' = ' . $this->db->sql_case($sql_subset_items, 0, $this->column_parent_id) . ',' : '') . ' + ' . $this->column_left_id . ' = ' . $set_left_id . ', + ' . $this->column_right_id . ' = ' . $set_right_id . ' + ' . ((!$set_subset_zero) ? ' WHERE ' . $sql_not_subset_items . ' ' . $this->get_sql_where('AND') : $this->get_sql_where('WHERE')); + $this->db->sql_query($sql); + + if ($acquired_new_lock) + { + $this->lock->release(); + } + } + + /** + * Prepare adding a subset to the nested set + * + * @param array $subset_items Subset of items to add + * @param array $new_parent Item containing the right bound of the new parent + * @return int New right id of the parent item + */ + protected function prepare_adding_subset(array $subset_items, array $new_parent) + { + $diff = sizeof($subset_items) * 2; + $sql_not_subset_items = $this->db->sql_in_set($this->column_item_id, $subset_items, true); + + $set_left_id = $this->db->sql_case($this->column_left_id . ' > ' . (int) $new_parent[$this->column_right_id], $this->column_left_id . ' + ' . $diff, $this->column_left_id); + $set_right_id = $this->db->sql_case($this->column_right_id . ' >= ' . (int) $new_parent[$this->column_right_id], $this->column_right_id . ' + ' . $diff, $this->column_right_id); + + $sql = 'UPDATE ' . $this->table_name . ' + SET ' . $this->column_left_id . ' = ' . $set_left_id . ', + ' . $this->column_right_id . ' = ' . $set_right_id . ' + WHERE ' . $sql_not_subset_items . ' + ' . $this->get_sql_where('AND'); + $this->db->sql_query($sql); + + return $new_parent[$this->column_right_id] + $diff; + } + + /** + * Resets values required for the nested set system + * + * @param array $item Original item data + * @return array Original item data + nested set defaults + */ + protected function reset_nestedset_values(array $item) + { + $item_data = array_merge($item, array( + $this->column_parent_id => 0, + $this->column_left_id => 0, + $this->column_right_id => 0, + $this->column_item_parents => '', + )); + + unset($item_data[$this->column_item_id]); + + return $item_data; + } + + /** + * Regenerate left/right ids from parent/child relationship + * + * This method regenerates the left/right ids for the tree based on + * the parent/child relations. This function executes three queries per + * item, so it should only be called, when the set has one of the following + * problems: + * - The set has a duplicated value inside the left/right id chain + * - The set has a missing value inside the left/right id chain + * - The set has items that do not have a left/right id set + * + * When regenerating the items, the items are sorted by parent id and their + * current left id, so the current child/parent relationships are kept + * and running the function on a working set will not change the order. + * + * @param int $new_id First left_id to be used (should start with 1) + * @param int $parent_id parent_id of the current set (default = 0) + * @param bool $reset_ids Should we reset all left_id/right_id on the first call? + * @return int $new_id The next left_id/right_id that should be used + */ + public function regenerate_left_right_ids($new_id, $parent_id = 0, $reset_ids = false) + { + if ($acquired_new_lock = $this->acquire_lock()) + { + $this->db->sql_transaction('begin'); + + if (!$reset_ids) + { + $sql = 'UPDATE ' . $this->table_name . ' + SET ' . $this->column_item_parents . " = '' + " . $this->get_sql_where('WHERE'); + $this->db->sql_query($sql); + } + } + + if ($reset_ids) + { + $sql = 'UPDATE ' . $this->table_name . ' + SET ' . $this->db->sql_build_array('UPDATE', array( + $this->column_left_id => 0, + $this->column_right_id => 0, + $this->column_item_parents => '', + )) . ' + ' . $this->get_sql_where('WHERE'); + $this->db->sql_query($sql); + } + + $sql = 'SELECT * + FROM ' . $this->table_name . ' + WHERE ' . $this->column_parent_id . ' = ' . (int) $parent_id . ' + ' . $this->get_sql_where('AND') . ' + ORDER BY ' . $this->column_left_id . ', ' . $this->column_item_id . ' ASC'; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + // First we update the left_id for this module + if ($row[$this->column_left_id] != $new_id) + { + $sql = 'UPDATE ' . $this->table_name . ' + SET ' . $this->db->sql_build_array('UPDATE', array($this->column_left_id => $new_id)) . ' + WHERE ' . $this->column_item_id . ' = ' . (int) $row[$this->column_item_id]; + $this->db->sql_query($sql); + } + $new_id++; + + // Then we go through any children and update their left/right id's + $new_id = $this->regenerate_left_right_ids($new_id, $row[$this->column_item_id]); + + // Then we come back and update the right_id for this module + if ($row[$this->column_right_id] != $new_id) + { + $sql = 'UPDATE ' . $this->table_name . ' + SET ' . $this->db->sql_build_array('UPDATE', array($this->column_right_id => $new_id)) . ' + WHERE ' . $this->column_item_id . ' = ' . (int) $row[$this->column_item_id]; + $this->db->sql_query($sql); + } + $new_id++; + } + $this->db->sql_freeresult($result); + + if ($acquired_new_lock) + { + $this->db->sql_transaction('commit'); + $this->lock->release(); + } + + return $new_id; + } +} diff --git a/phpBB/phpbb/tree/nestedset_forum.php b/phpBB/phpbb/tree/nestedset_forum.php new file mode 100644 index 0000000000..ff09ef55d0 --- /dev/null +++ b/phpBB/phpbb/tree/nestedset_forum.php @@ -0,0 +1,46 @@ + 'forum_id', + 'item_parents' => 'forum_parents', + ) + ); + } +} diff --git a/phpBB/phpbb/user.php b/phpBB/phpbb/user.php new file mode 100644 index 0000000000..5530fe3f03 --- /dev/null +++ b/phpBB/phpbb/user.php @@ -0,0 +1,857 @@ + 0, 'viewflash' => 1, 'viewsmilies' => 2, 'viewsigs' => 3, 'viewavatars' => 4, 'viewcensors' => 5, 'attachsig' => 6, 'bbcode' => 8, 'smilies' => 9, 'popuppm' => 10, 'sig_bbcode' => 15, 'sig_smilies' => 16, 'sig_links' => 17); + + /** + * Constructor to set the lang path + */ + function __construct() + { + global $phpbb_root_path; + + $this->lang_path = $phpbb_root_path . 'language/'; + } + + /** + * Function to set custom language path (able to use directory outside of phpBB) + * + * @param string $lang_path New language path used. + * @access public + */ + function set_custom_lang_path($lang_path) + { + $this->lang_path = $lang_path; + + if (substr($this->lang_path, -1) != '/') + { + $this->lang_path .= '/'; + } + } + + /** + * Setup basic user-specific items (style, language, ...) + */ + function setup($lang_set = false, $style_id = false) + { + global $db, $phpbb_style, $template, $config, $auth, $phpEx, $phpbb_root_path, $cache; + global $phpbb_dispatcher; + + if ($this->data['user_id'] != ANONYMOUS) + { + $user_lang_name = (file_exists($this->lang_path . $this->data['user_lang'] . "/common.$phpEx")) ? $this->data['user_lang'] : basename($config['default_lang']); + $user_date_format = $this->data['user_dateformat']; + $user_timezone = $this->data['user_timezone']; + } + else + { + $user_lang_name = basename($config['default_lang']); + $user_date_format = $config['default_dateformat']; + $user_timezone = $config['board_timezone']; + + /** + * If a guest user is surfing, we try to guess his/her language first by obtaining the browser language + * If re-enabled we need to make sure only those languages installed are checked + * Commented out so we do not loose the code. + + if ($request->header('Accept-Language')) + { + $accept_lang_ary = explode(',', $request->header('Accept-Language')); + + foreach ($accept_lang_ary as $accept_lang) + { + // Set correct format ... guess full xx_YY form + $accept_lang = substr($accept_lang, 0, 2) . '_' . strtoupper(substr($accept_lang, 3, 2)); + $accept_lang = basename($accept_lang); + + if (file_exists($this->lang_path . $accept_lang . "/common.$phpEx")) + { + $user_lang_name = $config['default_lang'] = $accept_lang; + break; + } + else + { + // No match on xx_YY so try xx + $accept_lang = substr($accept_lang, 0, 2); + $accept_lang = basename($accept_lang); + + if (file_exists($this->lang_path . $accept_lang . "/common.$phpEx")) + { + $user_lang_name = $config['default_lang'] = $accept_lang; + break; + } + } + } + } + */ + } + + $user_data = $this->data; + + /** + * Event to load language files and modify user data on every page + * + * @event core.user_setup + * @var array user_data Array with user's data row + * @var string user_lang_name Basename of the user's langauge + * @var string user_date_format User's date/time format + * @var string user_timezone User's timezone, should be one of + * http://www.php.net/manual/en/timezones.php + * @var mixed lang_set String or array of language files + * @var mixed style_id Style we are going to display + * @since 3.1-A1 + */ + $vars = array('user_data', 'user_lang_name', 'user_date_format', 'user_timezone', 'lang_set', 'style_id'); + extract($phpbb_dispatcher->trigger_event('core.user_setup', compact($vars))); + + $this->data = $user_data; + $this->lang_name = $user_lang_name; + $this->date_format = $user_date_format; + + try + { + $this->timezone = new DateTimeZone($user_timezone); + } + catch (Exception $e) + { + // If the timezone the user has selected is invalid, we fall back to UTC. + $this->timezone = new DateTimeZone('UTC'); + } + + // We include common language file here to not load it every time a custom language file is included + $lang = &$this->lang; + + // Do not suppress error if in DEBUG mode + $include_result = (defined('DEBUG')) ? (include $this->lang_path . $this->lang_name . "/common.$phpEx") : (@include $this->lang_path . $this->lang_name . "/common.$phpEx"); + + if ($include_result === false) + { + die('Language file ' . $this->lang_path . $this->lang_name . "/common.$phpEx" . " couldn't be opened."); + } + + $this->add_lang($lang_set); + unset($lang_set); + + $style_request = request_var('style', 0); + if ($style_request && $auth->acl_get('a_styles') && !defined('ADMIN_START')) + { + global $SID, $_EXTRA_URL; + + $style_id = $style_request; + $SID .= '&style=' . $style_id; + $_EXTRA_URL = array('style=' . $style_id); + } + else + { + // Set up style + $style_id = ($style_id) ? $style_id : ((!$config['override_user_style']) ? $this->data['user_style'] : $config['default_style']); + } + + $sql = 'SELECT * + FROM ' . STYLES_TABLE . " s + WHERE s.style_id = $style_id"; + $result = $db->sql_query($sql, 3600); + $this->style = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + // User has wrong style + if (!$this->style && $style_id == $this->data['user_style']) + { + $style_id = $this->data['user_style'] = $config['default_style']; + + $sql = 'UPDATE ' . USERS_TABLE . " + SET user_style = $style_id + WHERE user_id = {$this->data['user_id']}"; + $db->sql_query($sql); + + $sql = 'SELECT * + FROM ' . STYLES_TABLE . " s + WHERE s.style_id = $style_id"; + $result = $db->sql_query($sql, 3600); + $this->style = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + } + + if (!$this->style) + { + trigger_error('NO_STYLE_DATA', E_USER_ERROR); + } + + // Now parse the cfg file and cache it + $parsed_items = $cache->obtain_cfg_items($this->style); + + $check_for = array( + 'pagination_sep' => (string) ', ' + ); + + foreach ($check_for as $key => $default_value) + { + $this->style[$key] = (isset($parsed_items[$key])) ? $parsed_items[$key] : $default_value; + settype($this->style[$key], gettype($default_value)); + + if (is_string($default_value)) + { + $this->style[$key] = htmlspecialchars($this->style[$key]); + } + } + + $phpbb_style->set_style(); + + $this->img_lang = $this->lang_name; + + // Call phpbb_user_session_handler() in case external application want to "bend" some variables or replace classes... + // After calling it we continue script execution... + phpbb_user_session_handler(); + + // If this function got called from the error handler we are finished here. + if (defined('IN_ERROR_HANDLER')) + { + return; + } + + // Disable board if the install/ directory is still present + // For the brave development army we do not care about this, else we need to comment out this everytime we develop locally + if (!defined('DEBUG') && !defined('ADMIN_START') && !defined('IN_INSTALL') && !defined('IN_LOGIN') && file_exists($phpbb_root_path . 'install') && !is_file($phpbb_root_path . 'install')) + { + // Adjust the message slightly according to the permissions + if ($auth->acl_gets('a_', 'm_') || $auth->acl_getf_global('m_')) + { + $message = 'REMOVE_INSTALL'; + } + else + { + $message = (!empty($config['board_disable_msg'])) ? $config['board_disable_msg'] : 'BOARD_DISABLE'; + } + trigger_error($message); + } + + // Is board disabled and user not an admin or moderator? + if ($config['board_disable'] && !defined('IN_LOGIN') && !$auth->acl_gets('a_', 'm_') && !$auth->acl_getf_global('m_')) + { + if ($this->data['is_bot']) + { + send_status_line(503, 'Service Unavailable'); + } + + $message = (!empty($config['board_disable_msg'])) ? $config['board_disable_msg'] : 'BOARD_DISABLE'; + trigger_error($message); + } + + // Is load exceeded? + if ($config['limit_load'] && $this->load !== false) + { + if ($this->load > floatval($config['limit_load']) && !defined('IN_LOGIN') && !defined('IN_ADMIN')) + { + // Set board disabled to true to let the admins/mods get the proper notification + $config['board_disable'] = '1'; + + if (!$auth->acl_gets('a_', 'm_') && !$auth->acl_getf_global('m_')) + { + if ($this->data['is_bot']) + { + send_status_line(503, 'Service Unavailable'); + } + trigger_error('BOARD_UNAVAILABLE'); + } + } + } + + if (isset($this->data['session_viewonline'])) + { + // Make sure the user is able to hide his session + if (!$this->data['session_viewonline']) + { + // Reset online status if not allowed to hide the session... + if (!$auth->acl_get('u_hideonline')) + { + $sql = 'UPDATE ' . SESSIONS_TABLE . ' + SET session_viewonline = 1 + WHERE session_user_id = ' . $this->data['user_id']; + $db->sql_query($sql); + $this->data['session_viewonline'] = 1; + } + } + else if (!$this->data['user_allow_viewonline']) + { + // the user wants to hide and is allowed to -> cloaking device on. + if ($auth->acl_get('u_hideonline')) + { + $sql = 'UPDATE ' . SESSIONS_TABLE . ' + SET session_viewonline = 0 + WHERE session_user_id = ' . $this->data['user_id']; + $db->sql_query($sql); + $this->data['session_viewonline'] = 0; + } + } + } + + + // Does the user need to change their password? If so, redirect to the + // ucp profile reg_details page ... of course do not redirect if we're already in the ucp + if (!defined('IN_ADMIN') && !defined('ADMIN_START') && $config['chg_passforce'] && !empty($this->data['is_registered']) && $auth->acl_get('u_chgpasswd') && $this->data['user_passchg'] < time() - ($config['chg_passforce'] * 86400)) + { + if (strpos($this->page['query_string'], 'mode=reg_details') === false && $this->page['page_name'] != "ucp.$phpEx") + { + redirect(append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=profile&mode=reg_details')); + } + } + + return; + } + + /** + * More advanced language substitution + * Function to mimic sprintf() with the possibility of using phpBB's language system to substitute nullar/singular/plural forms. + * Params are the language key and the parameters to be substituted. + * This function/functionality is inspired by SHS` and Ashe. + * + * Example call: $user->lang('NUM_POSTS_IN_QUEUE', 1); + * + * If the first parameter is an array, the elements are used as keys and subkeys to get the language entry: + * Example: $user->lang(array('datetime', 'AGO'), 1) uses $user->lang['datetime']['AGO'] as language entry. + */ + function lang() + { + $args = func_get_args(); + $key = $args[0]; + + if (is_array($key)) + { + $lang = &$this->lang[array_shift($key)]; + + foreach ($key as $_key) + { + $lang = &$lang[$_key]; + } + } + else + { + $lang = &$this->lang[$key]; + } + + // Return if language string does not exist + if (!isset($lang) || (!is_string($lang) && !is_array($lang))) + { + return $key; + } + + // If the language entry is a string, we simply mimic sprintf() behaviour + if (is_string($lang)) + { + if (sizeof($args) == 1) + { + return $lang; + } + + // Replace key with language entry and simply pass along... + $args[0] = $lang; + return call_user_func_array('sprintf', $args); + } + else if (sizeof($lang) == 0) + { + // If the language entry is an empty array, we just return the language key + return $args[0]; + } + + // It is an array... now handle different nullar/singular/plural forms + $key_found = false; + + // We now get the first number passed and will select the key based upon this number + for ($i = 1, $num_args = sizeof($args); $i < $num_args; $i++) + { + if (is_int($args[$i]) || is_float($args[$i])) + { + if ($args[$i] == 0 && isset($lang[0])) + { + // We allow each translation using plural forms to specify a version for the case of 0 things, + // so that "0 users" may be displayed as "No users". + $key_found = 0; + break; + } + else + { + $use_plural_form = $this->get_plural_form($args[$i]); + if (isset($lang[$use_plural_form])) + { + // The key we should use exists, so we use it. + $key_found = $use_plural_form; + } + else + { + // If the key we need to use does not exist, we fall back to the previous one. + $numbers = array_keys($lang); + + foreach ($numbers as $num) + { + if ($num > $use_plural_form) + { + break; + } + + $key_found = $num; + } + } + break; + } + } + } + + // Ok, let's check if the key was found, else use the last entry (because it is mostly the plural form) + if ($key_found === false) + { + $numbers = array_keys($lang); + $key_found = end($numbers); + } + + // Use the language string we determined and pass it to sprintf() + $args[0] = $lang[$key_found]; + return call_user_func_array('sprintf', $args); + } + + /** + * Determine which plural form we should use. + * For some languages this is not as simple as for English. + * + * @param $number int|float The number we want to get the plural case for. Float numbers are floored. + * @param $force_rule mixed False to use the plural rule of the language package + * or an integer to force a certain plural rule + * @return int The plural-case we need to use for the number plural-rule combination + */ + function get_plural_form($number, $force_rule = false) + { + $number = (int) $number; + + // Default to English system + $plural_rule = ($force_rule !== false) ? $force_rule : ((isset($this->lang['PLURAL_RULE'])) ? $this->lang['PLURAL_RULE'] : 1); + + return phpbb_get_plural_form($plural_rule, $number); + } + + /** + * Add Language Items - use_db and use_help are assigned where needed (only use them to force inclusion) + * + * @param mixed $lang_set specifies the language entries to include + * @param bool $use_db internal variable for recursion, do not use + * @param bool $use_help internal variable for recursion, do not use + * @param string $ext_name The extension to load language from, or empty for core files + * + * Examples: + * + * $lang_set = array('posting', 'help' => 'faq'); + * $lang_set = array('posting', 'viewtopic', 'help' => array('bbcode', 'faq')) + * $lang_set = array(array('posting', 'viewtopic'), 'help' => array('bbcode', 'faq')) + * $lang_set = 'posting' + * $lang_set = array('help' => 'faq', 'db' => array('help:faq', 'posting')) + * + */ + function add_lang($lang_set, $use_db = false, $use_help = false, $ext_name = '') + { + global $phpEx; + + if (is_array($lang_set)) + { + foreach ($lang_set as $key => $lang_file) + { + // Please do not delete this line. + // We have to force the type here, else [array] language inclusion will not work + $key = (string) $key; + + if ($key == 'db') + { + $this->add_lang($lang_file, true, $use_help, $ext_name); + } + else if ($key == 'help') + { + $this->add_lang($lang_file, $use_db, true, $ext_name); + } + else if (!is_array($lang_file)) + { + $this->set_lang($this->lang, $this->help, $lang_file, $use_db, $use_help, $ext_name); + } + else + { + $this->add_lang($lang_file, $use_db, $use_help, $ext_name); + } + } + unset($lang_set); + } + else if ($lang_set) + { + $this->set_lang($this->lang, $this->help, $lang_set, $use_db, $use_help, $ext_name); + } + } + + /** + * Add Language Items from an extension - use_db and use_help are assigned where needed (only use them to force inclusion) + * + * @param string $ext_name The extension to load language from, or empty for core files + * @param mixed $lang_set specifies the language entries to include + * @param bool $use_db internal variable for recursion, do not use + * @param bool $use_help internal variable for recursion, do not use + */ + function add_lang_ext($ext_name, $lang_set, $use_db = false, $use_help = false) + { + if ($ext_name === '/') + { + $ext_name = ''; + } + + $this->add_lang($lang_set, $use_db, $use_help, $ext_name); + } + + /** + * Set language entry (called by add_lang) + * @access private + */ + function set_lang(&$lang, &$help, $lang_file, $use_db = false, $use_help = false, $ext_name = '') + { + global $phpbb_root_path, $phpEx; + + // Make sure the language name is set (if the user setup did not happen it is not set) + if (!$this->lang_name) + { + global $config; + $this->lang_name = basename($config['default_lang']); + } + + // $lang == $this->lang + // $help == $this->help + // - add appropriate variables here, name them as they are used within the language file... + if (!$use_db) + { + if ($use_help && strpos($lang_file, '/') !== false) + { + $filename = dirname($lang_file) . '/help_' . basename($lang_file); + } + else + { + $filename = (($use_help) ? 'help_' : '') . $lang_file; + } + + if ($ext_name) + { + global $phpbb_extension_manager; + $ext_path = $phpbb_extension_manager->get_extension_path($ext_name, true); + + $lang_path = $ext_path . 'language/'; + } + else + { + $lang_path = $this->lang_path; + } + + if (strpos($phpbb_root_path . $filename, $lang_path . $this->lang_name . '/') === 0) + { + $language_filename = $phpbb_root_path . $filename; + } + else + { + $language_filename = $lang_path . $this->lang_name . '/' . $filename . '.' . $phpEx; + } + + if (!file_exists($language_filename)) + { + global $config; + + if ($this->lang_name == 'en') + { + // The user's selected language is missing the file, the board default's language is missing the file, and the file doesn't exist in /en. + $language_filename = str_replace($lang_path . 'en', $lang_path . $this->data['user_lang'], $language_filename); + trigger_error('Language file ' . $language_filename . ' couldn\'t be opened.', E_USER_ERROR); + } + else if ($this->lang_name == basename($config['default_lang'])) + { + // Fall back to the English Language + $this->lang_name = 'en'; + $this->set_lang($lang, $help, $lang_file, $use_db, $use_help, $ext_name); + } + else if ($this->lang_name == $this->data['user_lang']) + { + // Fall back to the board default language + $this->lang_name = basename($config['default_lang']); + $this->set_lang($lang, $help, $lang_file, $use_db, $use_help, $ext_name); + } + + // Reset the lang name + $this->lang_name = (file_exists($lang_path . $this->data['user_lang'] . "/common.$phpEx")) ? $this->data['user_lang'] : basename($config['default_lang']); + return; + } + + // Do not suppress error if in DEBUG mode + $include_result = (defined('DEBUG')) ? (include $language_filename) : (@include $language_filename); + + if ($include_result === false) + { + trigger_error('Language file ' . $language_filename . ' couldn\'t be opened.', E_USER_ERROR); + } + } + else if ($use_db) + { + // Get Database Language Strings + // Put them into $lang if nothing is prefixed, put them into $help if help: is prefixed + // For example: help:faq, posting + } + } + + /** + * Format user date + * + * @param int $gmepoch unix timestamp + * @param string $format date format in date() notation. | used to indicate relative dates, for example |d m Y|, h:i is translated to Today, h:i. + * @param bool $forcedate force non-relative date format. + * + * @return mixed translated date + */ + function format_date($gmepoch, $format = false, $forcedate = false) + { + static $utc; + + if (!isset($utc)) + { + $utc = new DateTimeZone('UTC'); + } + + $time = new phpbb_datetime($this, "@$gmepoch", $utc); + $time->setTimezone($this->timezone); + + return $time->format($format, $forcedate); + } + + /** + * Create a phpbb_datetime object in the context of the current user + * + * @since 3.1 + * @param string $time String in a format accepted by strtotime(). + * @param DateTimeZone $timezone Time zone of the time. + * @return phpbb_datetime Date time object linked to the current users locale + */ + public function create_datetime($time = 'now', DateTimeZone $timezone = null) + { + $timezone = $timezone ?: $this->timezone; + return new phpbb_datetime($this, $time, $timezone); + } + + /** + * Get the UNIX timestamp for a datetime in the users timezone, so we can store it in the database. + * + * @param string $format Format of the entered date/time + * @param string $time Date/time with the timezone applied + * @param DateTimeZone $timezone Timezone of the date/time, falls back to timezone of current user + * @return int Returns the unix timestamp + */ + public function get_timestamp_from_format($format, $time, DateTimeZone $timezone = null) + { + $timezone = $timezone ?: $this->timezone; + $date = DateTime::createFromFormat($format, $time, $timezone); + return ($date !== false) ? $date->format('U') : false; + } + + /** + * Get language id currently used by the user + */ + function get_iso_lang_id() + { + global $config, $db; + + if (!empty($this->lang_id)) + { + return $this->lang_id; + } + + if (!$this->lang_name) + { + $this->lang_name = $config['default_lang']; + } + + $sql = 'SELECT lang_id + FROM ' . LANG_TABLE . " + WHERE lang_iso = '" . $db->sql_escape($this->lang_name) . "'"; + $result = $db->sql_query($sql); + $this->lang_id = (int) $db->sql_fetchfield('lang_id'); + $db->sql_freeresult($result); + + return $this->lang_id; + } + + /** + * Get users profile fields + */ + function get_profile_fields($user_id) + { + global $db; + + if (isset($this->profile_fields)) + { + return; + } + + $sql = 'SELECT * + FROM ' . PROFILE_FIELDS_DATA_TABLE . " + WHERE user_id = $user_id"; + $result = $db->sql_query_limit($sql, 1); + $this->profile_fields = (!($row = $db->sql_fetchrow($result))) ? array() : $row; + $db->sql_freeresult($result); + } + + /** + * Specify/Get image + */ + function img($img, $alt = '') + { + $alt = (!empty($this->lang[$alt])) ? $this->lang[$alt] : $alt; + return '' . $alt . ''; + } + + /** + * Get option bit field from user options. + * + * @param int $key option key, as defined in $keyoptions property. + * @param int $data bit field value to use, or false to use $this->data['user_options'] + * @return bool true if the option is set in the bit field, false otherwise + */ + function optionget($key, $data = false) + { + $var = ($data !== false) ? $data : $this->data['user_options']; + return phpbb_optionget($this->keyoptions[$key], $var); + } + + /** + * Set option bit field for user options. + * + * @param int $key Option key, as defined in $keyoptions property. + * @param bool $value True to set the option, false to clear the option. + * @param int $data Current bit field value, or false to use $this->data['user_options'] + * @return int|bool If $data is false, the bit field is modified and + * written back to $this->data['user_options'], and + * return value is true if the bit field changed and + * false otherwise. If $data is not false, the new + * bitfield value is returned. + */ + function optionset($key, $value, $data = false) + { + $var = ($data !== false) ? $data : $this->data['user_options']; + + $new_var = phpbb_optionset($this->keyoptions[$key], $value, $var); + + if ($data === false) + { + if ($new_var != $var) + { + $this->data['user_options'] = $new_var; + return true; + } + else + { + return false; + } + } + else + { + return $new_var; + } + } + + /** + * Funtion to make the user leave the NEWLY_REGISTERED system group. + * @access public + */ + function leave_newly_registered() + { + global $db; + + if (empty($this->data['user_new'])) + { + return false; + } + + if (!function_exists('remove_newly_registered')) + { + global $phpbb_root_path, $phpEx; + + include($phpbb_root_path . 'includes/functions_user.' . $phpEx); + } + if ($group = remove_newly_registered($this->data['user_id'], $this->data)) + { + $this->data['group_id'] = $group; + + } + $this->data['user_permissions'] = ''; + $this->data['user_new'] = 0; + + return true; + } + + /** + * Returns all password protected forum ids the user is currently NOT authenticated for. + * + * @return array Array of forum ids + * @access public + */ + function get_passworded_forums() + { + global $db; + + $sql = 'SELECT f.forum_id, fa.user_id + FROM ' . FORUMS_TABLE . ' f + LEFT JOIN ' . FORUMS_ACCESS_TABLE . " fa + ON (fa.forum_id = f.forum_id + AND fa.session_id = '" . $db->sql_escape($this->session_id) . "') + WHERE f.forum_password <> ''"; + $result = $db->sql_query($sql); + + $forum_ids = array(); + while ($row = $db->sql_fetchrow($result)) + { + $forum_id = (int) $row['forum_id']; + + if ($row['user_id'] != $this->data['user_id']) + { + $forum_ids[$forum_id] = $forum_id; + } + } + $db->sql_freeresult($result); + + return $forum_ids; + } +} diff --git a/phpBB/phpbb/user_loader.php b/phpBB/phpbb/user_loader.php new file mode 100644 index 0000000000..37bf9648c1 --- /dev/null +++ b/phpBB/phpbb/user_loader.php @@ -0,0 +1,231 @@ +db = $db; + + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + + $this->users_table = $users_table; + } + + /** + * Load user helper + * + * @param array $user_ids + */ + public function load_users(array $user_ids) + { + $user_ids[] = ANONYMOUS; + + // Make user_ids unique and convert to integer. + $user_ids = array_map('intval', array_unique($user_ids)); + + // Do not load users we already have in $this->users + $user_ids = array_diff($user_ids, array_keys($this->users)); + + if (sizeof($user_ids)) + { + $sql = 'SELECT * + FROM ' . $this->users_table . ' + WHERE ' . $this->db->sql_in_set('user_id', $user_ids); + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $this->users[$row['user_id']] = $row; + } + $this->db->sql_freeresult($result); + } + } + + /** + * Load a user by username + * + * Stores the full data in the user cache so they do not need to be loaded again + * Returns the user id so you may use get_user() from the returned value + * + * @param string $username Raw username to load (will be cleaned) + * @return int User ID for the username + */ + public function load_user_by_username($username) + { + $sql = 'SELECT * + FROM ' . $this->users_table . " + WHERE username_clean = '" . $this->db->sql_escape(utf8_clean_string($username)) . "'"; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if ($row) + { + $this->users[$row['user_id']] = $row; + + return $row['user_id']; + } + + return ANONYMOUS; + } + + /** + * Get a user row from our users cache + * + * @param int $user_id User ID of the user you want to retreive + * @param bool $query Should we query the database if this user has not yet been loaded? + * Typically this should be left as false and you should make sure + * you load users ahead of time with load_users() + * @return array|bool Row from the database of the user or Anonymous if the user wasn't loaded/does not exist + * or bool False if the anonymous user was not loaded + */ + public function get_user($user_id, $query = false) + { + if (isset($this->users[$user_id])) + { + return $this->users[$user_id]; + } + // Query them if we must (if ANONYMOUS is sent as the user_id and we have not loaded Anonymous yet, we must load Anonymous as a last resort) + else if ($query || $user_id == ANONYMOUS) + { + $this->load_users(array($user_id)); + + return $this->get_user($user_id); + } + + return $this->get_user(ANONYMOUS); + } + + /** + * Get username + * + * @param int $user_id User ID of the user you want to retreive the username for + * @param string $mode The mode to load (same as get_username_string). One of the following: + * profile (for getting an url to the profile) + * username (for obtaining the username) + * colour (for obtaining the user colour) + * full (for obtaining a html string representing a coloured link to the users profile) + * no_profile (the same as full but forcing no profile link) + * @param string $guest_username Optional parameter to specify the guest username. It will be used in favor of the GUEST language variable then. + * @param string $custom_profile_url Optional parameter to specify a profile url. The user id get appended to this url as &u={user_id} + * @param bool $query Should we query the database if this user has not yet been loaded? + * Typically this should be left as false and you should make sure + * you load users ahead of time with load_users() + * @return string + */ + public function get_username($user_id, $mode, $guest_username = false, $custom_profile_url = false, $query = false) + { + if (!($user = $this->get_user($user_id, $query))) + { + return ''; + } + + return get_username_string($mode, $user['user_id'], $user['username'], $user['user_colour'], $guest_username, $custom_profile_url); + } + + /** + * Get avatar + * + * @param int $user_id User ID of the user you want to retreive the avatar for + * @param bool $query Should we query the database if this user has not yet been loaded? + * Typically this should be left as false and you should make sure + * you load users ahead of time with load_users() + * @return string + */ + public function get_avatar($user_id, $query = false) + { + if (!($user = $this->get_user($user_id, $query))) + { + return ''; + } + + if (!function_exists('get_user_avatar')) + { + include($this->phpbb_root_path . 'includes/functions_display.' . $this->php_ext); + } + + return get_user_avatar($user['user_avatar'], $user['user_avatar_type'], $user['user_avatar_width'], $user['user_avatar_height']); + } + + /** + * Get rank + * + * @param int $user_id User ID of the user you want to retreive the rank for + * @param bool $query Should we query the database if this user has not yet been loaded? + * Typically this should be left as false and you should make sure + * you load users ahead of time with load_users() + * @return array Array with keys 'rank_title', 'rank_img', and 'rank_img_src' + */ + public function get_rank($user_id, $query = false) + { + if (!($user = $this->get_user($user_id, $query))) + { + return ''; + } + + if (!function_exists('get_user_rank')) + { + include($this->phpbb_root_path . 'includes/functions_display.' . $this->php_ext); + } + + $rank = array( + 'rank_title', + 'rank_img', + 'rank_img_src', + ); + + get_user_rank($user['user_rank'], (($user['user_id'] == ANONYMOUS) ? false : $user['user_posts']), $rank['rank_title'], $rank['rank_img'], $rank['rank_img_src']); + + return $rank; + } +} -- cgit v1.2.1 From 057d860d07fe829104aa938338830787415bc1c6 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Sun, 14 Jul 2013 00:56:37 -0400 Subject: [ticket/11696] Rename db_tools.php so it can be autoloaded PHPBB3-11696 --- phpBB/config/services.yml | 1 - phpBB/includes/acp/acp_database.php | 4 - .../captcha/plugins/phpbb_captcha_qa_plugin.php | 12 +- phpBB/includes/db/db_tools.php | 2486 -------------------- phpBB/includes/db/tools.php | 2486 ++++++++++++++++++++ phpBB/includes/functions_install.php | 6 - phpBB/phpbb/search/fulltext_sphinx.php | 5 - 7 files changed, 2488 insertions(+), 2512 deletions(-) delete mode 100644 phpBB/includes/db/db_tools.php create mode 100644 phpBB/includes/db/tools.php (limited to 'phpBB') diff --git a/phpBB/config/services.yml b/phpBB/config/services.yml index 25fff79de3..c1579cfb57 100644 --- a/phpBB/config/services.yml +++ b/phpBB/config/services.yml @@ -126,7 +126,6 @@ services: - [sql_connect, [%dbal.dbhost%, %dbal.dbuser%, %dbal.dbpasswd%, %dbal.dbname%, %dbal.dbport%, false, %dbal.new_link%]] dbal.tools: - file: %core.root_path%includes/db/db_tools.%core.php_ext% class: phpbb_db_tools arguments: - @dbal.conn diff --git a/phpBB/includes/acp/acp_database.php b/phpBB/includes/acp/acp_database.php index ebcbd28a87..5d191b3d0f 100644 --- a/phpBB/includes/acp/acp_database.php +++ b/phpBB/includes/acp/acp_database.php @@ -28,10 +28,6 @@ class acp_database global $cache, $db, $user, $auth, $template, $table_prefix; global $config, $phpbb_root_path, $phpbb_admin_path, $phpEx; - if (!class_exists('phpbb_db_tools')) - { - require($phpbb_root_path . 'includes/db/db_tools.' . $phpEx); - } $this->db_tools = new phpbb_db_tools($db); $user->add_lang('acp/database'); diff --git a/phpBB/includes/captcha/plugins/phpbb_captcha_qa_plugin.php b/phpBB/includes/captcha/plugins/phpbb_captcha_qa_plugin.php index ec7636f511..6843f25d72 100644 --- a/phpBB/includes/captcha/plugins/phpbb_captcha_qa_plugin.php +++ b/phpBB/includes/captcha/plugins/phpbb_captcha_qa_plugin.php @@ -110,12 +110,8 @@ class phpbb_captcha_qa */ static public function is_installed() { - global $db, $phpbb_root_path, $phpEx; + global $db; - if (!class_exists('phpbb_db_tools', false)) - { - include("$phpbb_root_path/includes/db/db_tools.$phpEx"); - } $db_tool = new phpbb_db_tools($db); return $db_tool->sql_table_exists(CAPTCHA_QUESTIONS_TABLE); @@ -297,12 +293,8 @@ class phpbb_captcha_qa */ function install() { - global $db, $phpbb_root_path, $phpEx; + global $db; - if (!class_exists('phpbb_db_tools')) - { - include("$phpbb_root_path/includes/db/db_tools.$phpEx"); - } $db_tool = new phpbb_db_tools($db); $tables = array(CAPTCHA_QUESTIONS_TABLE, CAPTCHA_ANSWERS_TABLE, CAPTCHA_QA_CONFIRM_TABLE); diff --git a/phpBB/includes/db/db_tools.php b/phpBB/includes/db/db_tools.php deleted file mode 100644 index 983cdc18ea..0000000000 --- a/phpBB/includes/db/db_tools.php +++ /dev/null @@ -1,2486 +0,0 @@ - array( - 'INT:' => 'int(%d)', - 'BINT' => 'bigint(20)', - 'UINT' => 'mediumint(8) UNSIGNED', - 'UINT:' => 'int(%d) UNSIGNED', - 'TINT:' => 'tinyint(%d)', - 'USINT' => 'smallint(4) UNSIGNED', - 'BOOL' => 'tinyint(1) UNSIGNED', - 'VCHAR' => 'varchar(255)', - 'VCHAR:' => 'varchar(%d)', - 'CHAR:' => 'char(%d)', - 'XSTEXT' => 'text', - 'XSTEXT_UNI'=> 'varchar(100)', - 'STEXT' => 'text', - 'STEXT_UNI' => 'varchar(255)', - 'TEXT' => 'text', - 'TEXT_UNI' => 'text', - 'MTEXT' => 'mediumtext', - 'MTEXT_UNI' => 'mediumtext', - 'TIMESTAMP' => 'int(11) UNSIGNED', - 'DECIMAL' => 'decimal(5,2)', - 'DECIMAL:' => 'decimal(%d,2)', - 'PDECIMAL' => 'decimal(6,3)', - 'PDECIMAL:' => 'decimal(%d,3)', - 'VCHAR_UNI' => 'varchar(255)', - 'VCHAR_UNI:'=> 'varchar(%d)', - 'VCHAR_CI' => 'varchar(255)', - 'VARBINARY' => 'varbinary(255)', - ), - - 'mysql_40' => array( - 'INT:' => 'int(%d)', - 'BINT' => 'bigint(20)', - 'UINT' => 'mediumint(8) UNSIGNED', - 'UINT:' => 'int(%d) UNSIGNED', - 'TINT:' => 'tinyint(%d)', - 'USINT' => 'smallint(4) UNSIGNED', - 'BOOL' => 'tinyint(1) UNSIGNED', - 'VCHAR' => 'varbinary(255)', - 'VCHAR:' => 'varbinary(%d)', - 'CHAR:' => 'binary(%d)', - 'XSTEXT' => 'blob', - 'XSTEXT_UNI'=> 'blob', - 'STEXT' => 'blob', - 'STEXT_UNI' => 'blob', - 'TEXT' => 'blob', - 'TEXT_UNI' => 'blob', - 'MTEXT' => 'mediumblob', - 'MTEXT_UNI' => 'mediumblob', - 'TIMESTAMP' => 'int(11) UNSIGNED', - 'DECIMAL' => 'decimal(5,2)', - 'DECIMAL:' => 'decimal(%d,2)', - 'PDECIMAL' => 'decimal(6,3)', - 'PDECIMAL:' => 'decimal(%d,3)', - 'VCHAR_UNI' => 'blob', - 'VCHAR_UNI:'=> array('varbinary(%d)', 'limit' => array('mult', 3, 255, 'blob')), - 'VCHAR_CI' => 'blob', - 'VARBINARY' => 'varbinary(255)', - ), - - 'firebird' => array( - 'INT:' => 'INTEGER', - 'BINT' => 'DOUBLE PRECISION', - 'UINT' => 'INTEGER', - 'UINT:' => 'INTEGER', - 'TINT:' => 'INTEGER', - 'USINT' => 'INTEGER', - 'BOOL' => 'INTEGER', - 'VCHAR' => 'VARCHAR(255) CHARACTER SET NONE', - 'VCHAR:' => 'VARCHAR(%d) CHARACTER SET NONE', - 'CHAR:' => 'CHAR(%d) CHARACTER SET NONE', - 'XSTEXT' => 'BLOB SUB_TYPE TEXT CHARACTER SET NONE', - 'STEXT' => 'BLOB SUB_TYPE TEXT CHARACTER SET NONE', - 'TEXT' => 'BLOB SUB_TYPE TEXT CHARACTER SET NONE', - 'MTEXT' => 'BLOB SUB_TYPE TEXT CHARACTER SET NONE', - 'XSTEXT_UNI'=> 'VARCHAR(100) CHARACTER SET UTF8', - 'STEXT_UNI' => 'VARCHAR(255) CHARACTER SET UTF8', - 'TEXT_UNI' => 'BLOB SUB_TYPE TEXT CHARACTER SET UTF8', - 'MTEXT_UNI' => 'BLOB SUB_TYPE TEXT CHARACTER SET UTF8', - 'TIMESTAMP' => 'INTEGER', - 'DECIMAL' => 'DOUBLE PRECISION', - 'DECIMAL:' => 'DOUBLE PRECISION', - 'PDECIMAL' => 'DOUBLE PRECISION', - 'PDECIMAL:' => 'DOUBLE PRECISION', - 'VCHAR_UNI' => 'VARCHAR(255) CHARACTER SET UTF8', - 'VCHAR_UNI:'=> 'VARCHAR(%d) CHARACTER SET UTF8', - 'VCHAR_CI' => 'VARCHAR(255) CHARACTER SET UTF8', - 'VARBINARY' => 'CHAR(255) CHARACTER SET NONE', - ), - - 'mssql' => array( - 'INT:' => '[int]', - 'BINT' => '[float]', - 'UINT' => '[int]', - 'UINT:' => '[int]', - 'TINT:' => '[int]', - 'USINT' => '[int]', - 'BOOL' => '[int]', - 'VCHAR' => '[varchar] (255)', - 'VCHAR:' => '[varchar] (%d)', - 'CHAR:' => '[char] (%d)', - 'XSTEXT' => '[varchar] (1000)', - 'STEXT' => '[varchar] (3000)', - 'TEXT' => '[varchar] (8000)', - 'MTEXT' => '[text]', - 'XSTEXT_UNI'=> '[varchar] (100)', - 'STEXT_UNI' => '[varchar] (255)', - 'TEXT_UNI' => '[varchar] (4000)', - 'MTEXT_UNI' => '[text]', - 'TIMESTAMP' => '[int]', - 'DECIMAL' => '[float]', - 'DECIMAL:' => '[float]', - 'PDECIMAL' => '[float]', - 'PDECIMAL:' => '[float]', - 'VCHAR_UNI' => '[varchar] (255)', - 'VCHAR_UNI:'=> '[varchar] (%d)', - 'VCHAR_CI' => '[varchar] (255)', - 'VARBINARY' => '[varchar] (255)', - ), - - 'mssqlnative' => array( - 'INT:' => '[int]', - 'BINT' => '[float]', - 'UINT' => '[int]', - 'UINT:' => '[int]', - 'TINT:' => '[int]', - 'USINT' => '[int]', - 'BOOL' => '[int]', - 'VCHAR' => '[varchar] (255)', - 'VCHAR:' => '[varchar] (%d)', - 'CHAR:' => '[char] (%d)', - 'XSTEXT' => '[varchar] (1000)', - 'STEXT' => '[varchar] (3000)', - 'TEXT' => '[varchar] (8000)', - 'MTEXT' => '[text]', - 'XSTEXT_UNI'=> '[varchar] (100)', - 'STEXT_UNI' => '[varchar] (255)', - 'TEXT_UNI' => '[varchar] (4000)', - 'MTEXT_UNI' => '[text]', - 'TIMESTAMP' => '[int]', - 'DECIMAL' => '[float]', - 'DECIMAL:' => '[float]', - 'PDECIMAL' => '[float]', - 'PDECIMAL:' => '[float]', - 'VCHAR_UNI' => '[varchar] (255)', - 'VCHAR_UNI:'=> '[varchar] (%d)', - 'VCHAR_CI' => '[varchar] (255)', - 'VARBINARY' => '[varchar] (255)', - ), - - 'oracle' => array( - 'INT:' => 'number(%d)', - 'BINT' => 'number(20)', - 'UINT' => 'number(8)', - 'UINT:' => 'number(%d)', - 'TINT:' => 'number(%d)', - 'USINT' => 'number(4)', - 'BOOL' => 'number(1)', - 'VCHAR' => 'varchar2(255)', - 'VCHAR:' => 'varchar2(%d)', - 'CHAR:' => 'char(%d)', - 'XSTEXT' => 'varchar2(1000)', - 'STEXT' => 'varchar2(3000)', - 'TEXT' => 'clob', - 'MTEXT' => 'clob', - 'XSTEXT_UNI'=> 'varchar2(300)', - 'STEXT_UNI' => 'varchar2(765)', - 'TEXT_UNI' => 'clob', - 'MTEXT_UNI' => 'clob', - 'TIMESTAMP' => 'number(11)', - 'DECIMAL' => 'number(5, 2)', - 'DECIMAL:' => 'number(%d, 2)', - 'PDECIMAL' => 'number(6, 3)', - 'PDECIMAL:' => 'number(%d, 3)', - 'VCHAR_UNI' => 'varchar2(765)', - 'VCHAR_UNI:'=> array('varchar2(%d)', 'limit' => array('mult', 3, 765, 'clob')), - 'VCHAR_CI' => 'varchar2(255)', - 'VARBINARY' => 'raw(255)', - ), - - 'sqlite' => array( - 'INT:' => 'int(%d)', - 'BINT' => 'bigint(20)', - 'UINT' => 'INTEGER UNSIGNED', //'mediumint(8) UNSIGNED', - 'UINT:' => 'INTEGER UNSIGNED', // 'int(%d) UNSIGNED', - 'TINT:' => 'tinyint(%d)', - 'USINT' => 'INTEGER UNSIGNED', //'mediumint(4) UNSIGNED', - 'BOOL' => 'INTEGER UNSIGNED', //'tinyint(1) UNSIGNED', - 'VCHAR' => 'varchar(255)', - 'VCHAR:' => 'varchar(%d)', - 'CHAR:' => 'char(%d)', - 'XSTEXT' => 'text(65535)', - 'STEXT' => 'text(65535)', - 'TEXT' => 'text(65535)', - 'MTEXT' => 'mediumtext(16777215)', - 'XSTEXT_UNI'=> 'text(65535)', - 'STEXT_UNI' => 'text(65535)', - 'TEXT_UNI' => 'text(65535)', - 'MTEXT_UNI' => 'mediumtext(16777215)', - 'TIMESTAMP' => 'INTEGER UNSIGNED', //'int(11) UNSIGNED', - 'DECIMAL' => 'decimal(5,2)', - 'DECIMAL:' => 'decimal(%d,2)', - 'PDECIMAL' => 'decimal(6,3)', - 'PDECIMAL:' => 'decimal(%d,3)', - 'VCHAR_UNI' => 'varchar(255)', - 'VCHAR_UNI:'=> 'varchar(%d)', - 'VCHAR_CI' => 'varchar(255)', - 'VARBINARY' => 'blob', - ), - - 'postgres' => array( - 'INT:' => 'INT4', - 'BINT' => 'INT8', - 'UINT' => 'INT4', // unsigned - 'UINT:' => 'INT4', // unsigned - 'USINT' => 'INT2', // unsigned - 'BOOL' => 'INT2', // unsigned - 'TINT:' => 'INT2', - 'VCHAR' => 'varchar(255)', - 'VCHAR:' => 'varchar(%d)', - 'CHAR:' => 'char(%d)', - 'XSTEXT' => 'varchar(1000)', - 'STEXT' => 'varchar(3000)', - 'TEXT' => 'varchar(8000)', - 'MTEXT' => 'TEXT', - 'XSTEXT_UNI'=> 'varchar(100)', - 'STEXT_UNI' => 'varchar(255)', - 'TEXT_UNI' => 'varchar(4000)', - 'MTEXT_UNI' => 'TEXT', - 'TIMESTAMP' => 'INT4', // unsigned - 'DECIMAL' => 'decimal(5,2)', - 'DECIMAL:' => 'decimal(%d,2)', - 'PDECIMAL' => 'decimal(6,3)', - 'PDECIMAL:' => 'decimal(%d,3)', - 'VCHAR_UNI' => 'varchar(255)', - 'VCHAR_UNI:'=> 'varchar(%d)', - 'VCHAR_CI' => 'varchar_ci', - 'VARBINARY' => 'bytea', - ), - ); - - /** - * A list of types being unsigned for better reference in some db's - * @var array - */ - var $unsigned_types = array('UINT', 'UINT:', 'USINT', 'BOOL', 'TIMESTAMP'); - - /** - * A list of supported DBMS. We change this class to support more DBMS, the DBMS itself only need to follow some rules. - * @var array - */ - var $supported_dbms = array('firebird', 'mssql', 'mssqlnative', 'mysql_40', 'mysql_41', 'oracle', 'postgres', 'sqlite'); - - /** - * This is set to true if user only wants to return the 'to-be-executed' SQL statement(s) (as an array). - * This mode has no effect on some methods (inserting of data for example). This is expressed within the methods command. - */ - var $return_statements = false; - - /** - * Constructor. Set DB Object and set {@link $return_statements return_statements}. - * - * @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(phpbb_db_driver $db, $return_statements = false) - { - $this->db = $db; - $this->return_statements = $return_statements; - - // Determine mapping database type - switch ($this->db->sql_layer) - { - case 'mysql': - $this->sql_layer = 'mysql_40'; - break; - - case 'mysql4': - if (version_compare($this->db->sql_server_info(true), '4.1.3', '>=')) - { - $this->sql_layer = 'mysql_41'; - } - else - { - $this->sql_layer = 'mysql_40'; - } - break; - - case 'mysqli': - $this->sql_layer = 'mysql_41'; - break; - - case 'mssql': - case 'mssql_odbc': - $this->sql_layer = 'mssql'; - break; - - case 'mssqlnative': - $this->sql_layer = 'mssqlnative'; - break; - - default: - $this->sql_layer = $this->db->sql_layer; - break; - } - } - - /** - * Setter for {@link $return_statements return_statements}. - * - * @param bool $return_statements True if SQL should not be executed but returned as strings - * @return null - */ - public function set_return_statements($return_statements) - { - $this->return_statements = $return_statements; - } - - /** - * Gets a list of tables in the database. - * - * @return array Array of table names (all lower case) - */ - function sql_list_tables() - { - switch ($this->db->sql_layer) - { - case 'mysql': - case 'mysql4': - case 'mysqli': - $sql = 'SHOW TABLES'; - break; - - case 'sqlite': - $sql = 'SELECT name - FROM sqlite_master - WHERE type = "table"'; - break; - - case 'mssql': - case 'mssql_odbc': - case 'mssqlnative': - $sql = "SELECT name - FROM sysobjects - WHERE type='U'"; - break; - - case 'postgres': - $sql = 'SELECT relname - FROM pg_stat_user_tables'; - break; - - case 'firebird': - $sql = 'SELECT rdb$relation_name - FROM rdb$relations - WHERE rdb$view_source is null - AND rdb$system_flag = 0'; - break; - - case 'oracle': - $sql = 'SELECT table_name - FROM USER_TABLES'; - break; - } - - $result = $this->db->sql_query($sql); - - $tables = array(); - while ($row = $this->db->sql_fetchrow($result)) - { - $name = current($row); - $tables[$name] = $name; - } - $this->db->sql_freeresult($result); - - return $tables; - } - - /** - * Check if table exists - * - * - * @param string $table_name The table name to check for - * @return bool true if table exists, else false - */ - function sql_table_exists($table_name) - { - $this->db->sql_return_on_error(true); - $result = $this->db->sql_query_limit('SELECT * FROM ' . $table_name, 1); - $this->db->sql_return_on_error(false); - - if ($result) - { - $this->db->sql_freeresult($result); - return true; - } - - return false; - } - - /** - * Create SQL Table - * - * @param string $table_name The table name to create - * @param array $table_data Array containing table data. - * @return array Statements if $return_statements is true. - */ - function sql_create_table($table_name, $table_data) - { - // holds the DDL for a column - $columns = $statements = array(); - - if ($this->sql_table_exists($table_name)) - { - return $this->_sql_run_sql($statements); - } - - // Begin transaction - $statements[] = 'begin'; - - // Determine if we have created a PRIMARY KEY in the earliest - $primary_key_gen = false; - - // Determine if the table must be created with TEXTIMAGE - $create_textimage = false; - - // Determine if the table requires a sequence - $create_sequence = false; - - // Begin table sql statement - switch ($this->sql_layer) - { - case 'mssql': - case 'mssqlnative': - $table_sql = 'CREATE TABLE [' . $table_name . '] (' . "\n"; - break; - - default: - $table_sql = 'CREATE TABLE ' . $table_name . ' (' . "\n"; - break; - } - - // Iterate through the columns to create a table - foreach ($table_data['COLUMNS'] as $column_name => $column_data) - { - // here lies an array, filled with information compiled on the column's data - $prepared_column = $this->sql_prepare_column_data($table_name, $column_name, $column_data); - - if (isset($prepared_column['auto_increment']) && strlen($column_name) > 26) // "${column_name}_gen" - { - trigger_error("Index name '${column_name}_gen' on table '$table_name' is too long. The maximum auto increment column length is 26 characters.", E_USER_ERROR); - } - - // here we add the definition of the new column to the list of columns - switch ($this->sql_layer) - { - case 'mssql': - case 'mssqlnative': - $columns[] = "\t [{$column_name}] " . $prepared_column['column_type_sql_default']; - break; - - default: - $columns[] = "\t {$column_name} " . $prepared_column['column_type_sql']; - break; - } - - // see if we have found a primary key set due to a column definition if we have found it, we can stop looking - if (!$primary_key_gen) - { - $primary_key_gen = isset($prepared_column['primary_key_set']) && $prepared_column['primary_key_set']; - } - - // create textimage DDL based off of the existance of certain column types - if (!$create_textimage) - { - $create_textimage = isset($prepared_column['textimage']) && $prepared_column['textimage']; - } - - // create sequence DDL based off of the existance of auto incrementing columns - if (!$create_sequence && isset($prepared_column['auto_increment']) && $prepared_column['auto_increment']) - { - $create_sequence = $column_name; - } - } - - // this makes up all the columns in the create table statement - $table_sql .= implode(",\n", $columns); - - // Close the table for two DBMS and add to the statements - switch ($this->sql_layer) - { - case 'firebird': - $table_sql .= "\n);"; - $statements[] = $table_sql; - break; - - case 'mssql': - case 'mssqlnative': - $table_sql .= "\n) ON [PRIMARY]" . (($create_textimage) ? ' TEXTIMAGE_ON [PRIMARY]' : ''); - $statements[] = $table_sql; - break; - } - - // we have yet to create a primary key for this table, - // this means that we can add the one we really wanted instead - if (!$primary_key_gen) - { - // Write primary key - if (isset($table_data['PRIMARY_KEY'])) - { - if (!is_array($table_data['PRIMARY_KEY'])) - { - $table_data['PRIMARY_KEY'] = array($table_data['PRIMARY_KEY']); - } - - switch ($this->sql_layer) - { - case 'mysql_40': - case 'mysql_41': - case 'postgres': - case 'sqlite': - $table_sql .= ",\n\t PRIMARY KEY (" . implode(', ', $table_data['PRIMARY_KEY']) . ')'; - break; - - case 'firebird': - case 'mssql': - case 'mssqlnative': - // We need the data here - $old_return_statements = $this->return_statements; - $this->return_statements = true; - - $primary_key_stmts = $this->sql_create_primary_key($table_name, $table_data['PRIMARY_KEY']); - foreach ($primary_key_stmts as $pk_stmt) - { - $statements[] = $pk_stmt; - } - - $this->return_statements = $old_return_statements; - break; - - case 'oracle': - $table_sql .= ",\n\t CONSTRAINT pk_{$table_name} PRIMARY KEY (" . implode(', ', $table_data['PRIMARY_KEY']) . ')'; - break; - } - } - } - - // close the table - switch ($this->sql_layer) - { - case 'mysql_41': - // make sure the table is in UTF-8 mode - $table_sql .= "\n) CHARACTER SET `utf8` COLLATE `utf8_bin`;"; - $statements[] = $table_sql; - break; - - case 'mysql_40': - case 'sqlite': - $table_sql .= "\n);"; - $statements[] = $table_sql; - break; - - case 'postgres': - // do we need to add a sequence for auto incrementing columns? - if ($create_sequence) - { - $statements[] = "CREATE SEQUENCE {$table_name}_seq;"; - } - - $table_sql .= "\n);"; - $statements[] = $table_sql; - break; - - case 'oracle': - $table_sql .= "\n)"; - $statements[] = $table_sql; - - // do we need to add a sequence and a tigger for auto incrementing columns? - if ($create_sequence) - { - // create the actual sequence - $statements[] = "CREATE SEQUENCE {$table_name}_seq"; - - // the trigger is the mechanism by which we increment the counter - $trigger = "CREATE OR REPLACE TRIGGER t_{$table_name}\n"; - $trigger .= "BEFORE INSERT ON {$table_name}\n"; - $trigger .= "FOR EACH ROW WHEN (\n"; - $trigger .= "\tnew.{$create_sequence} IS NULL OR new.{$create_sequence} = 0\n"; - $trigger .= ")\n"; - $trigger .= "BEGIN\n"; - $trigger .= "\tSELECT {$table_name}_seq.nextval\n"; - $trigger .= "\tINTO :new.{$create_sequence}\n"; - $trigger .= "\tFROM dual;\n"; - $trigger .= "END;"; - - $statements[] = $trigger; - } - break; - - case 'firebird': - if ($create_sequence) - { - $statements[] = "CREATE GENERATOR {$table_name}_gen;"; - $statements[] = "SET GENERATOR {$table_name}_gen TO 0;"; - - $trigger = "CREATE TRIGGER t_$table_name FOR $table_name\n"; - $trigger .= "BEFORE INSERT\nAS\nBEGIN\n"; - $trigger .= "\tNEW.{$create_sequence} = GEN_ID({$table_name}_gen, 1);\nEND;"; - $statements[] = $trigger; - } - break; - } - - // Write Keys - if (isset($table_data['KEYS'])) - { - foreach ($table_data['KEYS'] as $key_name => $key_data) - { - if (!is_array($key_data[1])) - { - $key_data[1] = array($key_data[1]); - } - - $old_return_statements = $this->return_statements; - $this->return_statements = true; - - $key_stmts = ($key_data[0] == 'UNIQUE') ? $this->sql_create_unique_index($table_name, $key_name, $key_data[1]) : $this->sql_create_index($table_name, $key_name, $key_data[1]); - - foreach ($key_stmts as $key_stmt) - { - $statements[] = $key_stmt; - } - - $this->return_statements = $old_return_statements; - } - } - - // Commit Transaction - $statements[] = 'commit'; - - return $this->_sql_run_sql($statements); - } - - /** - * Handle passed database update array. - * Expected structure... - * Key being one of the following - * drop_tables: Drop tables - * add_tables: Add tables - * change_columns: Column changes (only type, not name) - * add_columns: Add columns to a table - * drop_keys: Dropping keys - * drop_columns: Removing/Dropping columns - * add_primary_keys: adding primary keys - * add_unique_index: adding an unique index - * add_index: adding an index (can be column:index_size if you need to provide size) - * - * The values are in this format: - * {TABLE NAME} => array( - * {COLUMN NAME} => array({COLUMN TYPE}, {DEFAULT VALUE}, {OPTIONAL VARIABLES}), - * {KEY/INDEX NAME} => array({COLUMN NAMES}), - * ) - * - * For more information have a look at /develop/create_schema_files.php (only available through SVN) - */ - function perform_schema_changes($schema_changes) - { - if (empty($schema_changes)) - { - return; - } - - $statements = array(); - $sqlite = false; - - // For SQLite we need to perform the schema changes in a much more different way - if ($this->db->sql_layer == 'sqlite' && $this->return_statements) - { - $sqlite_data = array(); - $sqlite = true; - } - - // Drop tables? - if (!empty($schema_changes['drop_tables'])) - { - foreach ($schema_changes['drop_tables'] as $table) - { - // only drop table if it exists - if ($this->sql_table_exists($table)) - { - $result = $this->sql_table_drop($table); - if ($this->return_statements) - { - $statements = array_merge($statements, $result); - } - } - } - } - - // Add tables? - if (!empty($schema_changes['add_tables'])) - { - foreach ($schema_changes['add_tables'] as $table => $table_data) - { - $result = $this->sql_create_table($table, $table_data); - if ($this->return_statements) - { - $statements = array_merge($statements, $result); - } - } - } - - // Change columns? - if (!empty($schema_changes['change_columns'])) - { - foreach ($schema_changes['change_columns'] as $table => $columns) - { - foreach ($columns as $column_name => $column_data) - { - // If the column exists we change it, else we add it ;) - if ($column_exists = $this->sql_column_exists($table, $column_name)) - { - $result = $this->sql_column_change($table, $column_name, $column_data, true); - } - else - { - $result = $this->sql_column_add($table, $column_name, $column_data, true); - } - - if ($sqlite) - { - if ($column_exists) - { - $sqlite_data[$table]['change_columns'][] = $result; - } - else - { - $sqlite_data[$table]['add_columns'][] = $result; - } - } - else if ($this->return_statements) - { - $statements = array_merge($statements, $result); - } - } - } - } - - // Add columns? - if (!empty($schema_changes['add_columns'])) - { - foreach ($schema_changes['add_columns'] as $table => $columns) - { - foreach ($columns as $column_name => $column_data) - { - // Only add the column if it does not exist yet - if ($column_exists = $this->sql_column_exists($table, $column_name)) - { - continue; - // This is commented out here because it can take tremendous time on updates -// $result = $this->sql_column_change($table, $column_name, $column_data, true); - } - else - { - $result = $this->sql_column_add($table, $column_name, $column_data, true); - } - - if ($sqlite) - { - if ($column_exists) - { - continue; -// $sqlite_data[$table]['change_columns'][] = $result; - } - else - { - $sqlite_data[$table]['add_columns'][] = $result; - } - } - else if ($this->return_statements) - { - $statements = array_merge($statements, $result); - } - } - } - } - - // Remove keys? - if (!empty($schema_changes['drop_keys'])) - { - foreach ($schema_changes['drop_keys'] as $table => $indexes) - { - foreach ($indexes as $index_name) - { - if (!$this->sql_index_exists($table, $index_name)) - { - continue; - } - - $result = $this->sql_index_drop($table, $index_name); - - if ($this->return_statements) - { - $statements = array_merge($statements, $result); - } - } - } - } - - // Drop columns? - if (!empty($schema_changes['drop_columns'])) - { - foreach ($schema_changes['drop_columns'] as $table => $columns) - { - foreach ($columns as $column) - { - // Only remove the column if it exists... - if ($this->sql_column_exists($table, $column)) - { - $result = $this->sql_column_remove($table, $column, true); - - if ($sqlite) - { - $sqlite_data[$table]['drop_columns'][] = $result; - } - else if ($this->return_statements) - { - $statements = array_merge($statements, $result); - } - } - } - } - } - - // Add primary keys? - if (!empty($schema_changes['add_primary_keys'])) - { - foreach ($schema_changes['add_primary_keys'] as $table => $columns) - { - $result = $this->sql_create_primary_key($table, $columns, true); - - if ($sqlite) - { - $sqlite_data[$table]['primary_key'] = $result; - } - else if ($this->return_statements) - { - $statements = array_merge($statements, $result); - } - } - } - - // Add unqiue indexes? - if (!empty($schema_changes['add_unique_index'])) - { - foreach ($schema_changes['add_unique_index'] as $table => $index_array) - { - foreach ($index_array as $index_name => $column) - { - if ($this->sql_unique_index_exists($table, $index_name)) - { - continue; - } - - $result = $this->sql_create_unique_index($table, $index_name, $column); - - if ($this->return_statements) - { - $statements = array_merge($statements, $result); - } - } - } - } - - // Add indexes? - if (!empty($schema_changes['add_index'])) - { - foreach ($schema_changes['add_index'] as $table => $index_array) - { - foreach ($index_array as $index_name => $column) - { - if ($this->sql_index_exists($table, $index_name)) - { - continue; - } - - $result = $this->sql_create_index($table, $index_name, $column); - - if ($this->return_statements) - { - $statements = array_merge($statements, $result); - } - } - } - } - - if ($sqlite) - { - foreach ($sqlite_data as $table_name => $sql_schema_changes) - { - // Create temporary table with original data - $statements[] = 'begin'; - - $sql = "SELECT sql - FROM sqlite_master - WHERE type = 'table' - AND name = '{$table_name}' - ORDER BY type DESC, name;"; - $result = $this->db->sql_query($sql); - - if (!$result) - { - continue; - } - - $row = $this->db->sql_fetchrow($result); - $this->db->sql_freeresult($result); - - // Create a backup table and populate it, destroy the existing one - $statements[] = preg_replace('#CREATE\s+TABLE\s+"?' . $table_name . '"?#i', 'CREATE TEMPORARY TABLE ' . $table_name . '_temp', $row['sql']); - $statements[] = 'INSERT INTO ' . $table_name . '_temp SELECT * FROM ' . $table_name; - $statements[] = 'DROP TABLE ' . $table_name; - - // Get the columns... - preg_match('#\((.*)\)#s', $row['sql'], $matches); - - $plain_table_cols = trim($matches[1]); - $new_table_cols = preg_split('/,(?![\s\w]+\))/m', $plain_table_cols); - $column_list = array(); - - foreach ($new_table_cols as $declaration) - { - $entities = preg_split('#\s+#', trim($declaration)); - if ($entities[0] == 'PRIMARY') - { - continue; - } - $column_list[] = $entities[0]; - } - - // note down the primary key notation because sqlite only supports adding it to the end for the new table - $primary_key = false; - $_new_cols = array(); - - foreach ($new_table_cols as $key => $declaration) - { - $entities = preg_split('#\s+#', trim($declaration)); - if ($entities[0] == 'PRIMARY') - { - $primary_key = $declaration; - continue; - } - $_new_cols[] = $declaration; - } - - $new_table_cols = $_new_cols; - - // First of all... change columns - if (!empty($sql_schema_changes['change_columns'])) - { - foreach ($sql_schema_changes['change_columns'] as $column_sql) - { - foreach ($new_table_cols as $key => $declaration) - { - $entities = preg_split('#\s+#', trim($declaration)); - if (strpos($column_sql, $entities[0] . ' ') === 0) - { - $new_table_cols[$key] = $column_sql; - } - } - } - } - - if (!empty($sql_schema_changes['add_columns'])) - { - foreach ($sql_schema_changes['add_columns'] as $column_sql) - { - $new_table_cols[] = $column_sql; - } - } - - // Now drop them... - if (!empty($sql_schema_changes['drop_columns'])) - { - foreach ($sql_schema_changes['drop_columns'] as $column_name) - { - // Remove from column list... - $new_column_list = array(); - foreach ($column_list as $key => $value) - { - if ($value === $column_name) - { - continue; - } - - $new_column_list[] = $value; - } - - $column_list = $new_column_list; - - // Remove from table... - $_new_cols = array(); - foreach ($new_table_cols as $key => $declaration) - { - $entities = preg_split('#\s+#', trim($declaration)); - if (strpos($column_name . ' ', $entities[0] . ' ') === 0) - { - continue; - } - $_new_cols[] = $declaration; - } - $new_table_cols = $_new_cols; - } - } - - // Primary key... - if (!empty($sql_schema_changes['primary_key'])) - { - $new_table_cols[] = 'PRIMARY KEY (' . implode(', ', $sql_schema_changes['primary_key']) . ')'; - } - // Add a new one or the old primary key - else if ($primary_key !== false) - { - $new_table_cols[] = $primary_key; - } - - $columns = implode(',', $column_list); - - // create a new table and fill it up. destroy the temp one - $statements[] = 'CREATE TABLE ' . $table_name . ' (' . implode(',', $new_table_cols) . ');'; - $statements[] = 'INSERT INTO ' . $table_name . ' (' . $columns . ') SELECT ' . $columns . ' FROM ' . $table_name . '_temp;'; - $statements[] = 'DROP TABLE ' . $table_name . '_temp'; - - $statements[] = 'commit'; - } - } - - if ($this->return_statements) - { - return $statements; - } - } - - /** - * Gets a list of columns of a table. - * - * @param string $table Table name - * - * @return array Array of column names (all lower case) - */ - function sql_list_columns($table) - { - $columns = array(); - - switch ($this->sql_layer) - { - case 'mysql_40': - case 'mysql_41': - $sql = "SHOW COLUMNS FROM $table"; - break; - - // PostgreSQL has a way of doing this in a much simpler way but would - // not allow us to support all versions of PostgreSQL - case 'postgres': - $sql = "SELECT a.attname - FROM pg_class c, pg_attribute a - WHERE c.relname = '{$table}' - AND a.attnum > 0 - AND a.attrelid = c.oid"; - break; - - // same deal with PostgreSQL, we must perform more complex operations than - // we technically could - case 'mssql': - case 'mssqlnative': - $sql = "SELECT c.name - FROM syscolumns c - LEFT JOIN sysobjects o ON c.id = o.id - WHERE o.name = '{$table}'"; - break; - - case 'oracle': - $sql = "SELECT column_name - FROM user_tab_columns - WHERE LOWER(table_name) = '" . strtolower($table) . "'"; - break; - - case 'firebird': - $sql = "SELECT RDB\$FIELD_NAME as FNAME - FROM RDB\$RELATION_FIELDS - WHERE RDB\$RELATION_NAME = '" . strtoupper($table) . "'"; - break; - - case 'sqlite': - $sql = "SELECT sql - FROM sqlite_master - WHERE type = 'table' - AND name = '{$table}'"; - - $result = $this->db->sql_query($sql); - - if (!$result) - { - return false; - } - - $row = $this->db->sql_fetchrow($result); - $this->db->sql_freeresult($result); - - preg_match('#\((.*)\)#s', $row['sql'], $matches); - - $cols = trim($matches[1]); - $col_array = preg_split('/,(?![\s\w]+\))/m', $cols); - - foreach ($col_array as $declaration) - { - $entities = preg_split('#\s+#', trim($declaration)); - if ($entities[0] == 'PRIMARY') - { - continue; - } - - $column = strtolower($entities[0]); - $columns[$column] = $column; - } - - return $columns; - break; - } - - $result = $this->db->sql_query($sql); - - while ($row = $this->db->sql_fetchrow($result)) - { - $column = strtolower(current($row)); - $columns[$column] = $column; - } - $this->db->sql_freeresult($result); - - return $columns; - } - - /** - * Check whether a specified column exist in a table - * - * @param string $table Table to check - * @param string $column_name Column to check - * - * @return bool True if column exists, false otherwise - */ - function sql_column_exists($table, $column_name) - { - $columns = $this->sql_list_columns($table); - - return isset($columns[$column_name]); - } - - /** - * Check if a specified index exists in table. Does not return PRIMARY KEY and UNIQUE indexes. - * - * @param string $table_name Table to check the index at - * @param string $index_name The index name to check - * - * @return bool True if index exists, else false - */ - function sql_index_exists($table_name, $index_name) - { - if ($this->sql_layer == 'mssql' || $this->sql_layer == 'mssqlnative') - { - $sql = "EXEC sp_statistics '$table_name'"; - $result = $this->db->sql_query($sql); - - while ($row = $this->db->sql_fetchrow($result)) - { - if ($row['TYPE'] == 3) - { - if (strtolower($row['INDEX_NAME']) == strtolower($index_name)) - { - $this->db->sql_freeresult($result); - return true; - } - } - } - $this->db->sql_freeresult($result); - - return false; - } - - switch ($this->sql_layer) - { - case 'firebird': - $sql = "SELECT LOWER(RDB\$INDEX_NAME) as index_name - FROM RDB\$INDICES - WHERE RDB\$RELATION_NAME = '" . strtoupper($table_name) . "' - AND RDB\$UNIQUE_FLAG IS NULL - AND RDB\$FOREIGN_KEY IS NULL"; - $col = 'index_name'; - break; - - case 'postgres': - $sql = "SELECT ic.relname as index_name - FROM pg_class bc, pg_class ic, pg_index i - WHERE (bc.oid = i.indrelid) - AND (ic.oid = i.indexrelid) - AND (bc.relname = '" . $table_name . "') - AND (i.indisunique != 't') - AND (i.indisprimary != 't')"; - $col = 'index_name'; - break; - - case 'mysql_40': - case 'mysql_41': - $sql = 'SHOW KEYS - FROM ' . $table_name; - $col = 'Key_name'; - break; - - case 'oracle': - $sql = "SELECT index_name - FROM user_indexes - WHERE table_name = '" . strtoupper($table_name) . "' - AND generated = 'N' - AND uniqueness = 'NONUNIQUE'"; - $col = 'index_name'; - break; - - case 'sqlite': - $sql = "PRAGMA index_list('" . $table_name . "');"; - $col = 'name'; - break; - } - - $result = $this->db->sql_query($sql); - while ($row = $this->db->sql_fetchrow($result)) - { - if (($this->sql_layer == 'mysql_40' || $this->sql_layer == 'mysql_41') && !$row['Non_unique']) - { - continue; - } - - // These DBMS prefix index name with the table name - switch ($this->sql_layer) - { - case 'firebird': - case 'oracle': - case 'postgres': - case 'sqlite': - $row[$col] = substr($row[$col], strlen($table_name) + 1); - break; - } - - if (strtolower($row[$col]) == strtolower($index_name)) - { - $this->db->sql_freeresult($result); - return true; - } - } - $this->db->sql_freeresult($result); - - return false; - } - - /** - * Check if a specified index exists in table. Does not return PRIMARY KEY and UNIQUE indexes. - * - * @param string $table_name Table to check the index at - * @param string $index_name The index name to check - * - * @return bool True if index exists, else false - */ - function sql_unique_index_exists($table_name, $index_name) - { - if ($this->sql_layer == 'mssql' || $this->sql_layer == 'mssqlnative') - { - $sql = "EXEC sp_statistics '$table_name'"; - $result = $this->db->sql_query($sql); - - while ($row = $this->db->sql_fetchrow($result)) - { - // Usually NON_UNIQUE is the column we want to check, but we allow for both - if ($row['TYPE'] == 3) - { - if (strtolower($row['INDEX_NAME']) == strtolower($index_name)) - { - $this->db->sql_freeresult($result); - return true; - } - } - } - $this->db->sql_freeresult($result); - return false; - } - - switch ($this->sql_layer) - { - case 'firebird': - $sql = "SELECT LOWER(RDB\$INDEX_NAME) as index_name - FROM RDB\$INDICES - WHERE RDB\$RELATION_NAME = '" . strtoupper($table_name) . "' - AND RDB\$UNIQUE_FLAG IS NOT NULL - AND RDB\$FOREIGN_KEY IS NULL"; - $col = 'index_name'; - break; - - case 'postgres': - $sql = "SELECT ic.relname as index_name, i.indisunique - FROM pg_class bc, pg_class ic, pg_index i - WHERE (bc.oid = i.indrelid) - AND (ic.oid = i.indexrelid) - AND (bc.relname = '" . $table_name . "') - AND (i.indisprimary != 't')"; - $col = 'index_name'; - break; - - case 'mysql_40': - case 'mysql_41': - $sql = 'SHOW KEYS - FROM ' . $table_name; - $col = 'Key_name'; - break; - - case 'oracle': - $sql = "SELECT index_name, table_owner - FROM user_indexes - WHERE table_name = '" . strtoupper($table_name) . "' - AND generated = 'N' - AND uniqueness = 'UNIQUE'"; - $col = 'index_name'; - break; - - case 'sqlite': - $sql = "PRAGMA index_list('" . $table_name . "');"; - $col = 'name'; - break; - } - - $result = $this->db->sql_query($sql); - while ($row = $this->db->sql_fetchrow($result)) - { - if (($this->sql_layer == 'mysql_40' || $this->sql_layer == 'mysql_41') && ($row['Non_unique'] || $row[$col] == 'PRIMARY')) - { - continue; - } - - if ($this->sql_layer == 'sqlite' && !$row['unique']) - { - continue; - } - - if ($this->sql_layer == 'postgres' && $row['indisunique'] != 't') - { - continue; - } - - // These DBMS prefix index name with the table name - switch ($this->sql_layer) - { - case 'oracle': - // Two cases here... prefixed with U_[table_owner] and not prefixed with table_name - if (strpos($row[$col], 'U_') === 0) - { - $row[$col] = substr($row[$col], strlen('U_' . $row['table_owner']) + 1); - } - else if (strpos($row[$col], strtoupper($table_name)) === 0) - { - $row[$col] = substr($row[$col], strlen($table_name) + 1); - } - break; - - case 'firebird': - case 'postgres': - case 'sqlite': - $row[$col] = substr($row[$col], strlen($table_name) + 1); - break; - } - - if (strtolower($row[$col]) == strtolower($index_name)) - { - $this->db->sql_freeresult($result); - return true; - } - } - $this->db->sql_freeresult($result); - - return false; - } - - /** - * Private method for performing sql statements (either execute them or return them) - * @access private - */ - function _sql_run_sql($statements) - { - if ($this->return_statements) - { - return $statements; - } - - // We could add error handling here... - foreach ($statements as $sql) - { - if ($sql === 'begin') - { - $this->db->sql_transaction('begin'); - } - else if ($sql === 'commit') - { - $this->db->sql_transaction('commit'); - } - else - { - $this->db->sql_query($sql); - } - } - - return true; - } - - /** - * Function to prepare some column information for better usage - * @access private - */ - function sql_prepare_column_data($table_name, $column_name, $column_data) - { - if (strlen($column_name) > 30) - { - trigger_error("Column name '$column_name' on table '$table_name' is too long. The maximum is 30 characters.", E_USER_ERROR); - } - - // Get type - if (strpos($column_data[0], ':') !== false) - { - list($orig_column_type, $column_length) = explode(':', $column_data[0]); - if (!is_array($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':'])) - { - $column_type = sprintf($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':'], $column_length); - } - else - { - if (isset($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['rule'])) - { - switch ($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['rule'][0]) - { - case 'div': - $column_length /= $this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['rule'][1]; - $column_length = ceil($column_length); - $column_type = sprintf($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':'][0], $column_length); - break; - } - } - - if (isset($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'])) - { - switch ($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'][0]) - { - case 'mult': - $column_length *= $this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'][1]; - if ($column_length > $this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'][2]) - { - $column_type = $this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'][3]; - } - else - { - $column_type = sprintf($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':'][0], $column_length); - } - break; - } - } - } - $orig_column_type .= ':'; - } - else - { - $orig_column_type = $column_data[0]; - $column_type = $this->dbms_type_map[$this->sql_layer][$column_data[0]]; - } - - // Adjust default value if db-dependent specified - if (is_array($column_data[1])) - { - $column_data[1] = (isset($column_data[1][$this->sql_layer])) ? $column_data[1][$this->sql_layer] : $column_data[1]['default']; - } - - $sql = ''; - - $return_array = array(); - - switch ($this->sql_layer) - { - case 'firebird': - $sql .= " {$column_type} "; - $return_array['column_type_sql_type'] = " {$column_type} "; - - if (!is_null($column_data[1])) - { - $sql .= 'DEFAULT ' . ((is_numeric($column_data[1])) ? $column_data[1] : "'{$column_data[1]}'") . ' '; - $return_array['column_type_sql_default'] = ((is_numeric($column_data[1])) ? $column_data[1] : "'{$column_data[1]}'") . ' '; - } - - $sql .= 'NOT NULL'; - - // This is a UNICODE column and thus should be given it's fair share - if (preg_match('/^X?STEXT_UNI|VCHAR_(CI|UNI:?)/', $column_data[0])) - { - $sql .= ' COLLATE UNICODE'; - } - - $return_array['auto_increment'] = false; - if (isset($column_data[2]) && $column_data[2] == 'auto_increment') - { - $return_array['auto_increment'] = true; - } - - break; - - case 'mssql': - case 'mssqlnative': - $sql .= " {$column_type} "; - $sql_default = " {$column_type} "; - - // For adding columns we need the default definition - if (!is_null($column_data[1])) - { - // For hexadecimal values do not use single quotes - if (strpos($column_data[1], '0x') === 0) - { - $return_array['default'] = 'DEFAULT (' . $column_data[1] . ') '; - $sql_default .= $return_array['default']; - } - else - { - $return_array['default'] = 'DEFAULT (' . ((is_numeric($column_data[1])) ? $column_data[1] : "'{$column_data[1]}'") . ') '; - $sql_default .= $return_array['default']; - } - } - - if (isset($column_data[2]) && $column_data[2] == 'auto_increment') - { -// $sql .= 'IDENTITY (1, 1) '; - $sql_default .= 'IDENTITY (1, 1) '; - } - - $return_array['textimage'] = $column_type === '[text]'; - - $sql .= 'NOT NULL'; - $sql_default .= 'NOT NULL'; - - $return_array['column_type_sql_default'] = $sql_default; - - break; - - case 'mysql_40': - case 'mysql_41': - $sql .= " {$column_type} "; - - // For hexadecimal values do not use single quotes - if (!is_null($column_data[1]) && substr($column_type, -4) !== 'text' && substr($column_type, -4) !== 'blob') - { - $sql .= (strpos($column_data[1], '0x') === 0) ? "DEFAULT {$column_data[1]} " : "DEFAULT '{$column_data[1]}' "; - } - $sql .= 'NOT NULL'; - - if (isset($column_data[2])) - { - if ($column_data[2] == 'auto_increment') - { - $sql .= ' auto_increment'; - } - else if ($this->sql_layer === 'mysql_41' && $column_data[2] == 'true_sort') - { - $sql .= ' COLLATE utf8_unicode_ci'; - } - } - - break; - - case 'oracle': - $sql .= " {$column_type} "; - $sql .= (!is_null($column_data[1])) ? "DEFAULT '{$column_data[1]}' " : ''; - - // In Oracle empty strings ('') are treated as NULL. - // Therefore in oracle we allow NULL's for all DEFAULT '' entries - // Oracle does not like setting NOT NULL on a column that is already NOT NULL (this happens only on number fields) - if (!preg_match('/number/i', $column_type)) - { - $sql .= ($column_data[1] === '') ? '' : 'NOT NULL'; - } - - $return_array['auto_increment'] = false; - if (isset($column_data[2]) && $column_data[2] == 'auto_increment') - { - $return_array['auto_increment'] = true; - } - - break; - - case 'postgres': - $return_array['column_type'] = $column_type; - - $sql .= " {$column_type} "; - - $return_array['auto_increment'] = false; - if (isset($column_data[2]) && $column_data[2] == 'auto_increment') - { - $default_val = "nextval('{$table_name}_seq')"; - $return_array['auto_increment'] = true; - } - else if (!is_null($column_data[1])) - { - $default_val = "'" . $column_data[1] . "'"; - $return_array['null'] = 'NOT NULL'; - $sql .= 'NOT NULL '; - } - - $return_array['default'] = $default_val; - - $sql .= "DEFAULT {$default_val}"; - - // Unsigned? Then add a CHECK contraint - if (in_array($orig_column_type, $this->unsigned_types)) - { - $return_array['constraint'] = "CHECK ({$column_name} >= 0)"; - $sql .= " CHECK ({$column_name} >= 0)"; - } - - break; - - case 'sqlite': - $return_array['primary_key_set'] = false; - if (isset($column_data[2]) && $column_data[2] == 'auto_increment') - { - $sql .= ' INTEGER PRIMARY KEY'; - $return_array['primary_key_set'] = true; - } - else - { - $sql .= ' ' . $column_type; - } - - $sql .= ' NOT NULL '; - $sql .= (!is_null($column_data[1])) ? "DEFAULT '{$column_data[1]}'" : ''; - - break; - } - - $return_array['column_type_sql'] = $sql; - - return $return_array; - } - - /** - * Add new column - */ - function sql_column_add($table_name, $column_name, $column_data, $inline = false) - { - $column_data = $this->sql_prepare_column_data($table_name, $column_name, $column_data); - $statements = array(); - - switch ($this->sql_layer) - { - case 'firebird': - // Does not support AFTER statement, only POSITION (and there you need the column position) - $statements[] = 'ALTER TABLE ' . $table_name . ' ADD "' . strtoupper($column_name) . '" ' . $column_data['column_type_sql']; - break; - - case 'mssql': - case 'mssqlnative': - // Does not support AFTER, only through temporary table - $statements[] = 'ALTER TABLE [' . $table_name . '] ADD [' . $column_name . '] ' . $column_data['column_type_sql_default']; - break; - - case 'mysql_40': - case 'mysql_41': - $after = (!empty($column_data['after'])) ? ' AFTER ' . $column_data['after'] : ''; - $statements[] = 'ALTER TABLE `' . $table_name . '` ADD COLUMN `' . $column_name . '` ' . $column_data['column_type_sql'] . $after; - break; - - case 'oracle': - // Does not support AFTER, only through temporary table - $statements[] = 'ALTER TABLE ' . $table_name . ' ADD ' . $column_name . ' ' . $column_data['column_type_sql']; - break; - - case 'postgres': - // Does not support AFTER, only through temporary table - if (version_compare($this->db->sql_server_info(true), '8.0', '>=')) - { - $statements[] = 'ALTER TABLE ' . $table_name . ' ADD COLUMN "' . $column_name . '" ' . $column_data['column_type_sql']; - } - else - { - // old versions cannot add columns with default and null information - $statements[] = 'ALTER TABLE ' . $table_name . ' ADD COLUMN "' . $column_name . '" ' . $column_data['column_type'] . ' ' . $column_data['constraint']; - - if (isset($column_data['null'])) - { - if ($column_data['null'] == 'NOT NULL') - { - $statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN ' . $column_name . ' SET NOT NULL'; - } - } - - if (isset($column_data['default'])) - { - $statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN ' . $column_name . ' SET DEFAULT ' . $column_data['default']; - } - } - - break; - - case 'sqlite': - - if ($inline && $this->return_statements) - { - return $column_name . ' ' . $column_data['column_type_sql']; - } - - if (version_compare(sqlite_libversion(), '3.0') == -1) - { - $sql = "SELECT sql - FROM sqlite_master - WHERE type = 'table' - AND name = '{$table_name}' - ORDER BY type DESC, name;"; - $result = $this->db->sql_query($sql); - - if (!$result) - { - break; - } - - $row = $this->db->sql_fetchrow($result); - $this->db->sql_freeresult($result); - - $statements[] = 'begin'; - - // Create a backup table and populate it, destroy the existing one - $statements[] = preg_replace('#CREATE\s+TABLE\s+"?' . $table_name . '"?#i', 'CREATE TEMPORARY TABLE ' . $table_name . '_temp', $row['sql']); - $statements[] = 'INSERT INTO ' . $table_name . '_temp SELECT * FROM ' . $table_name; - $statements[] = 'DROP TABLE ' . $table_name; - - preg_match('#\((.*)\)#s', $row['sql'], $matches); - - $new_table_cols = trim($matches[1]); - $old_table_cols = preg_split('/,(?![\s\w]+\))/m', $new_table_cols); - $column_list = array(); - - foreach ($old_table_cols as $declaration) - { - $entities = preg_split('#\s+#', trim($declaration)); - if ($entities[0] == 'PRIMARY') - { - continue; - } - $column_list[] = $entities[0]; - } - - $columns = implode(',', $column_list); - - $new_table_cols = $column_name . ' ' . $column_data['column_type_sql'] . ',' . $new_table_cols; - - // create a new table and fill it up. destroy the temp one - $statements[] = 'CREATE TABLE ' . $table_name . ' (' . $new_table_cols . ');'; - $statements[] = 'INSERT INTO ' . $table_name . ' (' . $columns . ') SELECT ' . $columns . ' FROM ' . $table_name . '_temp;'; - $statements[] = 'DROP TABLE ' . $table_name . '_temp'; - - $statements[] = 'commit'; - } - else - { - $statements[] = 'ALTER TABLE ' . $table_name . ' ADD ' . $column_name . ' [' . $column_data['column_type_sql'] . ']'; - } - break; - } - - return $this->_sql_run_sql($statements); - } - - /** - * Drop column - */ - function sql_column_remove($table_name, $column_name, $inline = false) - { - $statements = array(); - - switch ($this->sql_layer) - { - case 'firebird': - $statements[] = 'ALTER TABLE ' . $table_name . ' DROP "' . strtoupper($column_name) . '"'; - break; - - case 'mssql': - case 'mssqlnative': - // remove default cosntraints first - // http://msdn.microsoft.com/en-us/library/aa175912%28v=sql.80%29.aspx - $statements[] = "DECLARE @drop_default_name VARCHAR(100), @cmd VARCHAR(1000) - SET @drop_default_name = - (SELECT so.name FROM sysobjects so - JOIN sysconstraints sc ON so.id = sc.constid - WHERE object_name(so.parent_obj) = '{$table_name}' - AND so.xtype = 'D' - AND sc.colid = (SELECT colid FROM syscolumns - WHERE id = object_id('{$table_name}') - AND name = '{$column_name}')) - IF @drop_default_name <> '' - BEGIN - SET @cmd = 'ALTER TABLE [{$table_name}] DROP CONSTRAINT [' + @drop_default_name + ']' - EXEC(@cmd) - END"; - $statements[] = 'ALTER TABLE [' . $table_name . '] DROP COLUMN [' . $column_name . ']'; - break; - - case 'mysql_40': - case 'mysql_41': - $statements[] = 'ALTER TABLE `' . $table_name . '` DROP COLUMN `' . $column_name . '`'; - break; - - case 'oracle': - $statements[] = 'ALTER TABLE ' . $table_name . ' DROP COLUMN ' . $column_name; - break; - - case 'postgres': - $statements[] = 'ALTER TABLE ' . $table_name . ' DROP COLUMN "' . $column_name . '"'; - break; - - case 'sqlite': - - if ($inline && $this->return_statements) - { - return $column_name; - } - - if (version_compare(sqlite_libversion(), '3.0') == -1) - { - $sql = "SELECT sql - FROM sqlite_master - WHERE type = 'table' - AND name = '{$table_name}' - ORDER BY type DESC, name;"; - $result = $this->db->sql_query($sql); - - if (!$result) - { - break; - } - - $row = $this->db->sql_fetchrow($result); - $this->db->sql_freeresult($result); - - $statements[] = 'begin'; - - // Create a backup table and populate it, destroy the existing one - $statements[] = preg_replace('#CREATE\s+TABLE\s+"?' . $table_name . '"?#i', 'CREATE TEMPORARY TABLE ' . $table_name . '_temp', $row['sql']); - $statements[] = 'INSERT INTO ' . $table_name . '_temp SELECT * FROM ' . $table_name; - $statements[] = 'DROP TABLE ' . $table_name; - - preg_match('#\((.*)\)#s', $row['sql'], $matches); - - $new_table_cols = trim($matches[1]); - $old_table_cols = preg_split('/,(?![\s\w]+\))/m', $new_table_cols); - $column_list = array(); - - foreach ($old_table_cols as $declaration) - { - $entities = preg_split('#\s+#', trim($declaration)); - if ($entities[0] == 'PRIMARY' || $entities[0] === $column_name) - { - continue; - } - $column_list[] = $entities[0]; - } - - $columns = implode(',', $column_list); - - $new_table_cols = preg_replace('/' . $column_name . '[^,]+(?:,|$)/m', '', $new_table_cols); - - // create a new table and fill it up. destroy the temp one - $statements[] = 'CREATE TABLE ' . $table_name . ' (' . $new_table_cols . ');'; - $statements[] = 'INSERT INTO ' . $table_name . ' (' . $columns . ') SELECT ' . $columns . ' FROM ' . $table_name . '_temp;'; - $statements[] = 'DROP TABLE ' . $table_name . '_temp'; - - $statements[] = 'commit'; - } - else - { - $statements[] = 'ALTER TABLE ' . $table_name . ' DROP COLUMN ' . $column_name; - } - break; - } - - return $this->_sql_run_sql($statements); - } - - /** - * Drop Index - */ - function sql_index_drop($table_name, $index_name) - { - $statements = array(); - - switch ($this->sql_layer) - { - case 'mssql': - case 'mssqlnative': - $statements[] = 'DROP INDEX ' . $table_name . '.' . $index_name; - break; - - case 'mysql_40': - case 'mysql_41': - $statements[] = 'DROP INDEX ' . $index_name . ' ON ' . $table_name; - break; - - case 'firebird': - case 'oracle': - case 'postgres': - case 'sqlite': - $statements[] = 'DROP INDEX ' . $table_name . '_' . $index_name; - break; - } - - return $this->_sql_run_sql($statements); - } - - /** - * Drop Table - */ - function sql_table_drop($table_name) - { - $statements = array(); - - if (!$this->sql_table_exists($table_name)) - { - return $this->_sql_run_sql($statements); - } - - // the most basic operation, get rid of the table - $statements[] = 'DROP TABLE ' . $table_name; - - switch ($this->sql_layer) - { - case 'firebird': - $sql = 'SELECT RDB$GENERATOR_NAME as gen - FROM RDB$GENERATORS - WHERE RDB$SYSTEM_FLAG = 0 - AND RDB$GENERATOR_NAME = \'' . strtoupper($table_name) . "_GEN'"; - $result = $this->db->sql_query($sql); - - // does a generator exist? - if ($row = $this->db->sql_fetchrow($result)) - { - $statements[] = "DROP GENERATOR {$row['gen']};"; - } - $this->db->sql_freeresult($result); - break; - - case 'oracle': - $sql = 'SELECT A.REFERENCED_NAME - FROM USER_DEPENDENCIES A, USER_TRIGGERS B - WHERE A.REFERENCED_TYPE = \'SEQUENCE\' - AND A.NAME = B.TRIGGER_NAME - AND B.TABLE_NAME = \'' . strtoupper($table_name) . "'"; - $result = $this->db->sql_query($sql); - - // any sequences ref'd to this table's triggers? - while ($row = $this->db->sql_fetchrow($result)) - { - $statements[] = "DROP SEQUENCE {$row['referenced_name']}"; - } - $this->db->sql_freeresult($result); - break; - - case 'postgres': - // PGSQL does not "tightly" bind sequences and tables, we must guess... - $sql = "SELECT relname - FROM pg_class - WHERE relkind = 'S' - AND relname = '{$table_name}_seq'"; - $result = $this->db->sql_query($sql); - - // We don't even care about storing the results. We already know the answer if we get rows back. - if ($this->db->sql_fetchrow($result)) - { - $statements[] = "DROP SEQUENCE {$table_name}_seq;\n"; - } - $this->db->sql_freeresult($result); - break; - } - - return $this->_sql_run_sql($statements); - } - - /** - * Add primary key - */ - function sql_create_primary_key($table_name, $column, $inline = false) - { - $statements = array(); - - switch ($this->sql_layer) - { - case 'firebird': - case 'postgres': - case 'mysql_40': - case 'mysql_41': - $statements[] = 'ALTER TABLE ' . $table_name . ' ADD PRIMARY KEY (' . implode(', ', $column) . ')'; - break; - - case 'mssql': - case 'mssqlnative': - $sql = "ALTER TABLE [{$table_name}] WITH NOCHECK ADD "; - $sql .= "CONSTRAINT [PK_{$table_name}] PRIMARY KEY CLUSTERED ("; - $sql .= '[' . implode("],\n\t\t[", $column) . ']'; - $sql .= ') ON [PRIMARY]'; - - $statements[] = $sql; - break; - - case 'oracle': - $statements[] = 'ALTER TABLE ' . $table_name . 'add CONSTRAINT pk_' . $table_name . ' PRIMARY KEY (' . implode(', ', $column) . ')'; - break; - - case 'sqlite': - - if ($inline && $this->return_statements) - { - return $column; - } - - $sql = "SELECT sql - FROM sqlite_master - WHERE type = 'table' - AND name = '{$table_name}' - ORDER BY type DESC, name;"; - $result = $this->db->sql_query($sql); - - if (!$result) - { - break; - } - - $row = $this->db->sql_fetchrow($result); - $this->db->sql_freeresult($result); - - $statements[] = 'begin'; - - // Create a backup table and populate it, destroy the existing one - $statements[] = preg_replace('#CREATE\s+TABLE\s+"?' . $table_name . '"?#i', 'CREATE TEMPORARY TABLE ' . $table_name . '_temp', $row['sql']); - $statements[] = 'INSERT INTO ' . $table_name . '_temp SELECT * FROM ' . $table_name; - $statements[] = 'DROP TABLE ' . $table_name; - - preg_match('#\((.*)\)#s', $row['sql'], $matches); - - $new_table_cols = trim($matches[1]); - $old_table_cols = preg_split('/,(?![\s\w]+\))/m', $new_table_cols); - $column_list = array(); - - foreach ($old_table_cols as $declaration) - { - $entities = preg_split('#\s+#', trim($declaration)); - if ($entities[0] == 'PRIMARY') - { - continue; - } - $column_list[] = $entities[0]; - } - - $columns = implode(',', $column_list); - - // create a new table and fill it up. destroy the temp one - $statements[] = 'CREATE TABLE ' . $table_name . ' (' . $new_table_cols . ', PRIMARY KEY (' . implode(', ', $column) . '));'; - $statements[] = 'INSERT INTO ' . $table_name . ' (' . $columns . ') SELECT ' . $columns . ' FROM ' . $table_name . '_temp;'; - $statements[] = 'DROP TABLE ' . $table_name . '_temp'; - - $statements[] = 'commit'; - break; - } - - return $this->_sql_run_sql($statements); - } - - /** - * Add unique index - */ - function sql_create_unique_index($table_name, $index_name, $column) - { - $statements = array(); - - $table_prefix = substr(CONFIG_TABLE, 0, -6); // strlen(config) - if (strlen($table_name . $index_name) - strlen($table_prefix) > 24) - { - $max_length = strlen($table_prefix) + 24; - trigger_error("Index name '{$table_name}_$index_name' on table '$table_name' is too long. The maximum is $max_length characters.", E_USER_ERROR); - } - - switch ($this->sql_layer) - { - case 'firebird': - case 'postgres': - case 'oracle': - case 'sqlite': - $statements[] = 'CREATE UNIQUE INDEX ' . $table_name . '_' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ')'; - break; - - case 'mysql_40': - case 'mysql_41': - $statements[] = 'ALTER TABLE ' . $table_name . ' ADD UNIQUE INDEX ' . $index_name . '(' . implode(', ', $column) . ')'; - break; - - case 'mssql': - case 'mssqlnative': - $statements[] = 'CREATE UNIQUE INDEX ' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ') ON [PRIMARY]'; - break; - } - - return $this->_sql_run_sql($statements); - } - - /** - * Add index - */ - function sql_create_index($table_name, $index_name, $column) - { - $statements = array(); - - $table_prefix = substr(CONFIG_TABLE, 0, -6); // strlen(config) - if (strlen($table_name . $index_name) - strlen($table_prefix) > 24) - { - $max_length = strlen($table_prefix) + 24; - trigger_error("Index name '{$table_name}_$index_name' on table '$table_name' is too long. The maximum is $max_length characters.", E_USER_ERROR); - } - - // remove index length unless MySQL4 - if ('mysql_40' != $this->sql_layer) - { - $column = preg_replace('#:.*$#', '', $column); - } - - switch ($this->sql_layer) - { - case 'firebird': - case 'postgres': - case 'oracle': - case 'sqlite': - $statements[] = 'CREATE INDEX ' . $table_name . '_' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ')'; - break; - - case 'mysql_40': - // add index size to definition as required by MySQL4 - foreach ($column as $i => $col) - { - if (false !== strpos($col, ':')) - { - list($col, $index_size) = explode(':', $col); - $column[$i] = "$col($index_size)"; - } - } - // no break - case 'mysql_41': - $statements[] = 'ALTER TABLE ' . $table_name . ' ADD INDEX ' . $index_name . '(' . implode(', ', $column) . ')'; - break; - - case 'mssql': - case 'mssqlnative': - $statements[] = 'CREATE INDEX ' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ') ON [PRIMARY]'; - break; - } - - return $this->_sql_run_sql($statements); - } - - /** - * List all of the indices that belong to a table, - * does not count: - * * UNIQUE indices - * * PRIMARY keys - */ - function sql_list_index($table_name) - { - $index_array = array(); - - if ($this->sql_layer == 'mssql' || $this->sql_layer == 'mssqlnative') - { - $sql = "EXEC sp_statistics '$table_name'"; - $result = $this->db->sql_query($sql); - while ($row = $this->db->sql_fetchrow($result)) - { - if ($row['TYPE'] == 3) - { - $index_array[] = $row['INDEX_NAME']; - } - } - $this->db->sql_freeresult($result); - } - else - { - switch ($this->sql_layer) - { - case 'firebird': - $sql = "SELECT LOWER(RDB\$INDEX_NAME) as index_name - FROM RDB\$INDICES - WHERE RDB\$RELATION_NAME = '" . strtoupper($table_name) . "' - AND RDB\$UNIQUE_FLAG IS NULL - AND RDB\$FOREIGN_KEY IS NULL"; - $col = 'index_name'; - break; - - case 'postgres': - $sql = "SELECT ic.relname as index_name - FROM pg_class bc, pg_class ic, pg_index i - WHERE (bc.oid = i.indrelid) - AND (ic.oid = i.indexrelid) - AND (bc.relname = '" . $table_name . "') - AND (i.indisunique != 't') - AND (i.indisprimary != 't')"; - $col = 'index_name'; - break; - - case 'mysql_40': - case 'mysql_41': - $sql = 'SHOW KEYS - FROM ' . $table_name; - $col = 'Key_name'; - break; - - case 'oracle': - $sql = "SELECT index_name - FROM user_indexes - WHERE table_name = '" . strtoupper($table_name) . "' - AND generated = 'N' - AND uniqueness = 'NONUNIQUE'"; - $col = 'index_name'; - break; - - case 'sqlite': - $sql = "PRAGMA index_info('" . $table_name . "');"; - $col = 'name'; - break; - } - - $result = $this->db->sql_query($sql); - while ($row = $this->db->sql_fetchrow($result)) - { - if (($this->sql_layer == 'mysql_40' || $this->sql_layer == 'mysql_41') && !$row['Non_unique']) - { - continue; - } - - switch ($this->sql_layer) - { - case 'firebird': - case 'oracle': - case 'postgres': - case 'sqlite': - $row[$col] = substr($row[$col], strlen($table_name) + 1); - break; - } - - $index_array[] = $row[$col]; - } - $this->db->sql_freeresult($result); - } - - return array_map('strtolower', $index_array); - } - - /** - * Change column type (not name!) - */ - function sql_column_change($table_name, $column_name, $column_data, $inline = false) - { - $column_data = $this->sql_prepare_column_data($table_name, $column_name, $column_data); - $statements = array(); - - switch ($this->sql_layer) - { - case 'firebird': - // Change type... - if (!empty($column_data['column_type_sql_default'])) - { - $statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN "' . strtoupper($column_name) . '" TYPE ' . ' ' . $column_data['column_type_sql_type']; - $statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN "' . strtoupper($column_name) . '" SET DEFAULT ' . ' ' . $column_data['column_type_sql_default']; - } - else - { - // TODO: try to change pkey without removing trigger, generator or constraints. ATM this query may fail. - $statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN "' . strtoupper($column_name) . '" TYPE ' . ' ' . $column_data['column_type_sql_type']; - } - break; - - case 'mssql': - case 'mssqlnative': - $statements[] = 'ALTER TABLE [' . $table_name . '] ALTER COLUMN [' . $column_name . '] ' . $column_data['column_type_sql']; - - if (!empty($column_data['default'])) - { - // Using TRANSACT-SQL for this statement because we do not want to have colliding data if statements are executed at a later stage - $statements[] = "DECLARE @drop_default_name VARCHAR(100), @cmd VARCHAR(1000) - SET @drop_default_name = - (SELECT so.name FROM sysobjects so - JOIN sysconstraints sc ON so.id = sc.constid - WHERE object_name(so.parent_obj) = '{$table_name}' - AND so.xtype = 'D' - AND sc.colid = (SELECT colid FROM syscolumns - WHERE id = object_id('{$table_name}') - AND name = '{$column_name}')) - IF @drop_default_name <> '' - BEGIN - SET @cmd = 'ALTER TABLE [{$table_name}] DROP CONSTRAINT [' + @drop_default_name + ']' - EXEC(@cmd) - END - SET @cmd = 'ALTER TABLE [{$table_name}] ADD CONSTRAINT [DF_{$table_name}_{$column_name}_1] {$column_data['default']} FOR [{$column_name}]' - EXEC(@cmd)"; - } - break; - - case 'mysql_40': - case 'mysql_41': - $statements[] = 'ALTER TABLE `' . $table_name . '` CHANGE `' . $column_name . '` `' . $column_name . '` ' . $column_data['column_type_sql']; - break; - - case 'oracle': - $statements[] = 'ALTER TABLE ' . $table_name . ' MODIFY ' . $column_name . ' ' . $column_data['column_type_sql']; - break; - - case 'postgres': - $sql = 'ALTER TABLE ' . $table_name . ' '; - - $sql_array = array(); - $sql_array[] = 'ALTER COLUMN ' . $column_name . ' TYPE ' . $column_data['column_type']; - - if (isset($column_data['null'])) - { - if ($column_data['null'] == 'NOT NULL') - { - $sql_array[] = 'ALTER COLUMN ' . $column_name . ' SET NOT NULL'; - } - else if ($column_data['null'] == 'NULL') - { - $sql_array[] = 'ALTER COLUMN ' . $column_name . ' DROP NOT NULL'; - } - } - - if (isset($column_data['default'])) - { - $sql_array[] = 'ALTER COLUMN ' . $column_name . ' SET DEFAULT ' . $column_data['default']; - } - - // we don't want to double up on constraints if we change different number data types - if (isset($column_data['constraint'])) - { - $constraint_sql = "SELECT consrc as constraint_data - FROM pg_constraint, pg_class bc - WHERE conrelid = bc.oid - AND bc.relname = '{$table_name}' - AND NOT EXISTS ( - SELECT * - FROM pg_constraint as c, pg_inherits as i - WHERE i.inhrelid = pg_constraint.conrelid - AND c.conname = pg_constraint.conname - AND c.consrc = pg_constraint.consrc - AND c.conrelid = i.inhparent - )"; - - $constraint_exists = false; - - $result = $this->db->sql_query($constraint_sql); - while ($row = $this->db->sql_fetchrow($result)) - { - if (trim($row['constraint_data']) == trim($column_data['constraint'])) - { - $constraint_exists = true; - break; - } - } - $this->db->sql_freeresult($result); - - if (!$constraint_exists) - { - $sql_array[] = 'ADD ' . $column_data['constraint']; - } - } - - $sql .= implode(', ', $sql_array); - - $statements[] = $sql; - break; - - case 'sqlite': - - if ($inline && $this->return_statements) - { - return $column_name . ' ' . $column_data['column_type_sql']; - } - - $sql = "SELECT sql - FROM sqlite_master - WHERE type = 'table' - AND name = '{$table_name}' - ORDER BY type DESC, name;"; - $result = $this->db->sql_query($sql); - - if (!$result) - { - break; - } - - $row = $this->db->sql_fetchrow($result); - $this->db->sql_freeresult($result); - - $statements[] = 'begin'; - - // Create a temp table and populate it, destroy the existing one - $statements[] = preg_replace('#CREATE\s+TABLE\s+"?' . $table_name . '"?#i', 'CREATE TEMPORARY TABLE ' . $table_name . '_temp', $row['sql']); - $statements[] = 'INSERT INTO ' . $table_name . '_temp SELECT * FROM ' . $table_name; - $statements[] = 'DROP TABLE ' . $table_name; - - preg_match('#\((.*)\)#s', $row['sql'], $matches); - - $new_table_cols = trim($matches[1]); - $old_table_cols = preg_split('/,(?![\s\w]+\))/m', $new_table_cols); - $column_list = array(); - - foreach ($old_table_cols as $key => $declaration) - { - $entities = preg_split('#\s+#', trim($declaration)); - $column_list[] = $entities[0]; - if ($entities[0] == $column_name) - { - $old_table_cols[$key] = $column_name . ' ' . $column_data['column_type_sql']; - } - } - - $columns = implode(',', $column_list); - - // create a new table and fill it up. destroy the temp one - $statements[] = 'CREATE TABLE ' . $table_name . ' (' . implode(',', $old_table_cols) . ');'; - $statements[] = 'INSERT INTO ' . $table_name . ' (' . $columns . ') SELECT ' . $columns . ' FROM ' . $table_name . '_temp;'; - $statements[] = 'DROP TABLE ' . $table_name . '_temp'; - - $statements[] = 'commit'; - - break; - } - - return $this->_sql_run_sql($statements); - } -} diff --git a/phpBB/includes/db/tools.php b/phpBB/includes/db/tools.php new file mode 100644 index 0000000000..983cdc18ea --- /dev/null +++ b/phpBB/includes/db/tools.php @@ -0,0 +1,2486 @@ + array( + 'INT:' => 'int(%d)', + 'BINT' => 'bigint(20)', + 'UINT' => 'mediumint(8) UNSIGNED', + 'UINT:' => 'int(%d) UNSIGNED', + 'TINT:' => 'tinyint(%d)', + 'USINT' => 'smallint(4) UNSIGNED', + 'BOOL' => 'tinyint(1) UNSIGNED', + 'VCHAR' => 'varchar(255)', + 'VCHAR:' => 'varchar(%d)', + 'CHAR:' => 'char(%d)', + 'XSTEXT' => 'text', + 'XSTEXT_UNI'=> 'varchar(100)', + 'STEXT' => 'text', + 'STEXT_UNI' => 'varchar(255)', + 'TEXT' => 'text', + 'TEXT_UNI' => 'text', + 'MTEXT' => 'mediumtext', + 'MTEXT_UNI' => 'mediumtext', + 'TIMESTAMP' => 'int(11) UNSIGNED', + 'DECIMAL' => 'decimal(5,2)', + 'DECIMAL:' => 'decimal(%d,2)', + 'PDECIMAL' => 'decimal(6,3)', + 'PDECIMAL:' => 'decimal(%d,3)', + 'VCHAR_UNI' => 'varchar(255)', + 'VCHAR_UNI:'=> 'varchar(%d)', + 'VCHAR_CI' => 'varchar(255)', + 'VARBINARY' => 'varbinary(255)', + ), + + 'mysql_40' => array( + 'INT:' => 'int(%d)', + 'BINT' => 'bigint(20)', + 'UINT' => 'mediumint(8) UNSIGNED', + 'UINT:' => 'int(%d) UNSIGNED', + 'TINT:' => 'tinyint(%d)', + 'USINT' => 'smallint(4) UNSIGNED', + 'BOOL' => 'tinyint(1) UNSIGNED', + 'VCHAR' => 'varbinary(255)', + 'VCHAR:' => 'varbinary(%d)', + 'CHAR:' => 'binary(%d)', + 'XSTEXT' => 'blob', + 'XSTEXT_UNI'=> 'blob', + 'STEXT' => 'blob', + 'STEXT_UNI' => 'blob', + 'TEXT' => 'blob', + 'TEXT_UNI' => 'blob', + 'MTEXT' => 'mediumblob', + 'MTEXT_UNI' => 'mediumblob', + 'TIMESTAMP' => 'int(11) UNSIGNED', + 'DECIMAL' => 'decimal(5,2)', + 'DECIMAL:' => 'decimal(%d,2)', + 'PDECIMAL' => 'decimal(6,3)', + 'PDECIMAL:' => 'decimal(%d,3)', + 'VCHAR_UNI' => 'blob', + 'VCHAR_UNI:'=> array('varbinary(%d)', 'limit' => array('mult', 3, 255, 'blob')), + 'VCHAR_CI' => 'blob', + 'VARBINARY' => 'varbinary(255)', + ), + + 'firebird' => array( + 'INT:' => 'INTEGER', + 'BINT' => 'DOUBLE PRECISION', + 'UINT' => 'INTEGER', + 'UINT:' => 'INTEGER', + 'TINT:' => 'INTEGER', + 'USINT' => 'INTEGER', + 'BOOL' => 'INTEGER', + 'VCHAR' => 'VARCHAR(255) CHARACTER SET NONE', + 'VCHAR:' => 'VARCHAR(%d) CHARACTER SET NONE', + 'CHAR:' => 'CHAR(%d) CHARACTER SET NONE', + 'XSTEXT' => 'BLOB SUB_TYPE TEXT CHARACTER SET NONE', + 'STEXT' => 'BLOB SUB_TYPE TEXT CHARACTER SET NONE', + 'TEXT' => 'BLOB SUB_TYPE TEXT CHARACTER SET NONE', + 'MTEXT' => 'BLOB SUB_TYPE TEXT CHARACTER SET NONE', + 'XSTEXT_UNI'=> 'VARCHAR(100) CHARACTER SET UTF8', + 'STEXT_UNI' => 'VARCHAR(255) CHARACTER SET UTF8', + 'TEXT_UNI' => 'BLOB SUB_TYPE TEXT CHARACTER SET UTF8', + 'MTEXT_UNI' => 'BLOB SUB_TYPE TEXT CHARACTER SET UTF8', + 'TIMESTAMP' => 'INTEGER', + 'DECIMAL' => 'DOUBLE PRECISION', + 'DECIMAL:' => 'DOUBLE PRECISION', + 'PDECIMAL' => 'DOUBLE PRECISION', + 'PDECIMAL:' => 'DOUBLE PRECISION', + 'VCHAR_UNI' => 'VARCHAR(255) CHARACTER SET UTF8', + 'VCHAR_UNI:'=> 'VARCHAR(%d) CHARACTER SET UTF8', + 'VCHAR_CI' => 'VARCHAR(255) CHARACTER SET UTF8', + 'VARBINARY' => 'CHAR(255) CHARACTER SET NONE', + ), + + 'mssql' => array( + 'INT:' => '[int]', + 'BINT' => '[float]', + 'UINT' => '[int]', + 'UINT:' => '[int]', + 'TINT:' => '[int]', + 'USINT' => '[int]', + 'BOOL' => '[int]', + 'VCHAR' => '[varchar] (255)', + 'VCHAR:' => '[varchar] (%d)', + 'CHAR:' => '[char] (%d)', + 'XSTEXT' => '[varchar] (1000)', + 'STEXT' => '[varchar] (3000)', + 'TEXT' => '[varchar] (8000)', + 'MTEXT' => '[text]', + 'XSTEXT_UNI'=> '[varchar] (100)', + 'STEXT_UNI' => '[varchar] (255)', + 'TEXT_UNI' => '[varchar] (4000)', + 'MTEXT_UNI' => '[text]', + 'TIMESTAMP' => '[int]', + 'DECIMAL' => '[float]', + 'DECIMAL:' => '[float]', + 'PDECIMAL' => '[float]', + 'PDECIMAL:' => '[float]', + 'VCHAR_UNI' => '[varchar] (255)', + 'VCHAR_UNI:'=> '[varchar] (%d)', + 'VCHAR_CI' => '[varchar] (255)', + 'VARBINARY' => '[varchar] (255)', + ), + + 'mssqlnative' => array( + 'INT:' => '[int]', + 'BINT' => '[float]', + 'UINT' => '[int]', + 'UINT:' => '[int]', + 'TINT:' => '[int]', + 'USINT' => '[int]', + 'BOOL' => '[int]', + 'VCHAR' => '[varchar] (255)', + 'VCHAR:' => '[varchar] (%d)', + 'CHAR:' => '[char] (%d)', + 'XSTEXT' => '[varchar] (1000)', + 'STEXT' => '[varchar] (3000)', + 'TEXT' => '[varchar] (8000)', + 'MTEXT' => '[text]', + 'XSTEXT_UNI'=> '[varchar] (100)', + 'STEXT_UNI' => '[varchar] (255)', + 'TEXT_UNI' => '[varchar] (4000)', + 'MTEXT_UNI' => '[text]', + 'TIMESTAMP' => '[int]', + 'DECIMAL' => '[float]', + 'DECIMAL:' => '[float]', + 'PDECIMAL' => '[float]', + 'PDECIMAL:' => '[float]', + 'VCHAR_UNI' => '[varchar] (255)', + 'VCHAR_UNI:'=> '[varchar] (%d)', + 'VCHAR_CI' => '[varchar] (255)', + 'VARBINARY' => '[varchar] (255)', + ), + + 'oracle' => array( + 'INT:' => 'number(%d)', + 'BINT' => 'number(20)', + 'UINT' => 'number(8)', + 'UINT:' => 'number(%d)', + 'TINT:' => 'number(%d)', + 'USINT' => 'number(4)', + 'BOOL' => 'number(1)', + 'VCHAR' => 'varchar2(255)', + 'VCHAR:' => 'varchar2(%d)', + 'CHAR:' => 'char(%d)', + 'XSTEXT' => 'varchar2(1000)', + 'STEXT' => 'varchar2(3000)', + 'TEXT' => 'clob', + 'MTEXT' => 'clob', + 'XSTEXT_UNI'=> 'varchar2(300)', + 'STEXT_UNI' => 'varchar2(765)', + 'TEXT_UNI' => 'clob', + 'MTEXT_UNI' => 'clob', + 'TIMESTAMP' => 'number(11)', + 'DECIMAL' => 'number(5, 2)', + 'DECIMAL:' => 'number(%d, 2)', + 'PDECIMAL' => 'number(6, 3)', + 'PDECIMAL:' => 'number(%d, 3)', + 'VCHAR_UNI' => 'varchar2(765)', + 'VCHAR_UNI:'=> array('varchar2(%d)', 'limit' => array('mult', 3, 765, 'clob')), + 'VCHAR_CI' => 'varchar2(255)', + 'VARBINARY' => 'raw(255)', + ), + + 'sqlite' => array( + 'INT:' => 'int(%d)', + 'BINT' => 'bigint(20)', + 'UINT' => 'INTEGER UNSIGNED', //'mediumint(8) UNSIGNED', + 'UINT:' => 'INTEGER UNSIGNED', // 'int(%d) UNSIGNED', + 'TINT:' => 'tinyint(%d)', + 'USINT' => 'INTEGER UNSIGNED', //'mediumint(4) UNSIGNED', + 'BOOL' => 'INTEGER UNSIGNED', //'tinyint(1) UNSIGNED', + 'VCHAR' => 'varchar(255)', + 'VCHAR:' => 'varchar(%d)', + 'CHAR:' => 'char(%d)', + 'XSTEXT' => 'text(65535)', + 'STEXT' => 'text(65535)', + 'TEXT' => 'text(65535)', + 'MTEXT' => 'mediumtext(16777215)', + 'XSTEXT_UNI'=> 'text(65535)', + 'STEXT_UNI' => 'text(65535)', + 'TEXT_UNI' => 'text(65535)', + 'MTEXT_UNI' => 'mediumtext(16777215)', + 'TIMESTAMP' => 'INTEGER UNSIGNED', //'int(11) UNSIGNED', + 'DECIMAL' => 'decimal(5,2)', + 'DECIMAL:' => 'decimal(%d,2)', + 'PDECIMAL' => 'decimal(6,3)', + 'PDECIMAL:' => 'decimal(%d,3)', + 'VCHAR_UNI' => 'varchar(255)', + 'VCHAR_UNI:'=> 'varchar(%d)', + 'VCHAR_CI' => 'varchar(255)', + 'VARBINARY' => 'blob', + ), + + 'postgres' => array( + 'INT:' => 'INT4', + 'BINT' => 'INT8', + 'UINT' => 'INT4', // unsigned + 'UINT:' => 'INT4', // unsigned + 'USINT' => 'INT2', // unsigned + 'BOOL' => 'INT2', // unsigned + 'TINT:' => 'INT2', + 'VCHAR' => 'varchar(255)', + 'VCHAR:' => 'varchar(%d)', + 'CHAR:' => 'char(%d)', + 'XSTEXT' => 'varchar(1000)', + 'STEXT' => 'varchar(3000)', + 'TEXT' => 'varchar(8000)', + 'MTEXT' => 'TEXT', + 'XSTEXT_UNI'=> 'varchar(100)', + 'STEXT_UNI' => 'varchar(255)', + 'TEXT_UNI' => 'varchar(4000)', + 'MTEXT_UNI' => 'TEXT', + 'TIMESTAMP' => 'INT4', // unsigned + 'DECIMAL' => 'decimal(5,2)', + 'DECIMAL:' => 'decimal(%d,2)', + 'PDECIMAL' => 'decimal(6,3)', + 'PDECIMAL:' => 'decimal(%d,3)', + 'VCHAR_UNI' => 'varchar(255)', + 'VCHAR_UNI:'=> 'varchar(%d)', + 'VCHAR_CI' => 'varchar_ci', + 'VARBINARY' => 'bytea', + ), + ); + + /** + * A list of types being unsigned for better reference in some db's + * @var array + */ + var $unsigned_types = array('UINT', 'UINT:', 'USINT', 'BOOL', 'TIMESTAMP'); + + /** + * A list of supported DBMS. We change this class to support more DBMS, the DBMS itself only need to follow some rules. + * @var array + */ + var $supported_dbms = array('firebird', 'mssql', 'mssqlnative', 'mysql_40', 'mysql_41', 'oracle', 'postgres', 'sqlite'); + + /** + * This is set to true if user only wants to return the 'to-be-executed' SQL statement(s) (as an array). + * This mode has no effect on some methods (inserting of data for example). This is expressed within the methods command. + */ + var $return_statements = false; + + /** + * Constructor. Set DB Object and set {@link $return_statements return_statements}. + * + * @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(phpbb_db_driver $db, $return_statements = false) + { + $this->db = $db; + $this->return_statements = $return_statements; + + // Determine mapping database type + switch ($this->db->sql_layer) + { + case 'mysql': + $this->sql_layer = 'mysql_40'; + break; + + case 'mysql4': + if (version_compare($this->db->sql_server_info(true), '4.1.3', '>=')) + { + $this->sql_layer = 'mysql_41'; + } + else + { + $this->sql_layer = 'mysql_40'; + } + break; + + case 'mysqli': + $this->sql_layer = 'mysql_41'; + break; + + case 'mssql': + case 'mssql_odbc': + $this->sql_layer = 'mssql'; + break; + + case 'mssqlnative': + $this->sql_layer = 'mssqlnative'; + break; + + default: + $this->sql_layer = $this->db->sql_layer; + break; + } + } + + /** + * Setter for {@link $return_statements return_statements}. + * + * @param bool $return_statements True if SQL should not be executed but returned as strings + * @return null + */ + public function set_return_statements($return_statements) + { + $this->return_statements = $return_statements; + } + + /** + * Gets a list of tables in the database. + * + * @return array Array of table names (all lower case) + */ + function sql_list_tables() + { + switch ($this->db->sql_layer) + { + case 'mysql': + case 'mysql4': + case 'mysqli': + $sql = 'SHOW TABLES'; + break; + + case 'sqlite': + $sql = 'SELECT name + FROM sqlite_master + WHERE type = "table"'; + break; + + case 'mssql': + case 'mssql_odbc': + case 'mssqlnative': + $sql = "SELECT name + FROM sysobjects + WHERE type='U'"; + break; + + case 'postgres': + $sql = 'SELECT relname + FROM pg_stat_user_tables'; + break; + + case 'firebird': + $sql = 'SELECT rdb$relation_name + FROM rdb$relations + WHERE rdb$view_source is null + AND rdb$system_flag = 0'; + break; + + case 'oracle': + $sql = 'SELECT table_name + FROM USER_TABLES'; + break; + } + + $result = $this->db->sql_query($sql); + + $tables = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $name = current($row); + $tables[$name] = $name; + } + $this->db->sql_freeresult($result); + + return $tables; + } + + /** + * Check if table exists + * + * + * @param string $table_name The table name to check for + * @return bool true if table exists, else false + */ + function sql_table_exists($table_name) + { + $this->db->sql_return_on_error(true); + $result = $this->db->sql_query_limit('SELECT * FROM ' . $table_name, 1); + $this->db->sql_return_on_error(false); + + if ($result) + { + $this->db->sql_freeresult($result); + return true; + } + + return false; + } + + /** + * Create SQL Table + * + * @param string $table_name The table name to create + * @param array $table_data Array containing table data. + * @return array Statements if $return_statements is true. + */ + function sql_create_table($table_name, $table_data) + { + // holds the DDL for a column + $columns = $statements = array(); + + if ($this->sql_table_exists($table_name)) + { + return $this->_sql_run_sql($statements); + } + + // Begin transaction + $statements[] = 'begin'; + + // Determine if we have created a PRIMARY KEY in the earliest + $primary_key_gen = false; + + // Determine if the table must be created with TEXTIMAGE + $create_textimage = false; + + // Determine if the table requires a sequence + $create_sequence = false; + + // Begin table sql statement + switch ($this->sql_layer) + { + case 'mssql': + case 'mssqlnative': + $table_sql = 'CREATE TABLE [' . $table_name . '] (' . "\n"; + break; + + default: + $table_sql = 'CREATE TABLE ' . $table_name . ' (' . "\n"; + break; + } + + // Iterate through the columns to create a table + foreach ($table_data['COLUMNS'] as $column_name => $column_data) + { + // here lies an array, filled with information compiled on the column's data + $prepared_column = $this->sql_prepare_column_data($table_name, $column_name, $column_data); + + if (isset($prepared_column['auto_increment']) && strlen($column_name) > 26) // "${column_name}_gen" + { + trigger_error("Index name '${column_name}_gen' on table '$table_name' is too long. The maximum auto increment column length is 26 characters.", E_USER_ERROR); + } + + // here we add the definition of the new column to the list of columns + switch ($this->sql_layer) + { + case 'mssql': + case 'mssqlnative': + $columns[] = "\t [{$column_name}] " . $prepared_column['column_type_sql_default']; + break; + + default: + $columns[] = "\t {$column_name} " . $prepared_column['column_type_sql']; + break; + } + + // see if we have found a primary key set due to a column definition if we have found it, we can stop looking + if (!$primary_key_gen) + { + $primary_key_gen = isset($prepared_column['primary_key_set']) && $prepared_column['primary_key_set']; + } + + // create textimage DDL based off of the existance of certain column types + if (!$create_textimage) + { + $create_textimage = isset($prepared_column['textimage']) && $prepared_column['textimage']; + } + + // create sequence DDL based off of the existance of auto incrementing columns + if (!$create_sequence && isset($prepared_column['auto_increment']) && $prepared_column['auto_increment']) + { + $create_sequence = $column_name; + } + } + + // this makes up all the columns in the create table statement + $table_sql .= implode(",\n", $columns); + + // Close the table for two DBMS and add to the statements + switch ($this->sql_layer) + { + case 'firebird': + $table_sql .= "\n);"; + $statements[] = $table_sql; + break; + + case 'mssql': + case 'mssqlnative': + $table_sql .= "\n) ON [PRIMARY]" . (($create_textimage) ? ' TEXTIMAGE_ON [PRIMARY]' : ''); + $statements[] = $table_sql; + break; + } + + // we have yet to create a primary key for this table, + // this means that we can add the one we really wanted instead + if (!$primary_key_gen) + { + // Write primary key + if (isset($table_data['PRIMARY_KEY'])) + { + if (!is_array($table_data['PRIMARY_KEY'])) + { + $table_data['PRIMARY_KEY'] = array($table_data['PRIMARY_KEY']); + } + + switch ($this->sql_layer) + { + case 'mysql_40': + case 'mysql_41': + case 'postgres': + case 'sqlite': + $table_sql .= ",\n\t PRIMARY KEY (" . implode(', ', $table_data['PRIMARY_KEY']) . ')'; + break; + + case 'firebird': + case 'mssql': + case 'mssqlnative': + // We need the data here + $old_return_statements = $this->return_statements; + $this->return_statements = true; + + $primary_key_stmts = $this->sql_create_primary_key($table_name, $table_data['PRIMARY_KEY']); + foreach ($primary_key_stmts as $pk_stmt) + { + $statements[] = $pk_stmt; + } + + $this->return_statements = $old_return_statements; + break; + + case 'oracle': + $table_sql .= ",\n\t CONSTRAINT pk_{$table_name} PRIMARY KEY (" . implode(', ', $table_data['PRIMARY_KEY']) . ')'; + break; + } + } + } + + // close the table + switch ($this->sql_layer) + { + case 'mysql_41': + // make sure the table is in UTF-8 mode + $table_sql .= "\n) CHARACTER SET `utf8` COLLATE `utf8_bin`;"; + $statements[] = $table_sql; + break; + + case 'mysql_40': + case 'sqlite': + $table_sql .= "\n);"; + $statements[] = $table_sql; + break; + + case 'postgres': + // do we need to add a sequence for auto incrementing columns? + if ($create_sequence) + { + $statements[] = "CREATE SEQUENCE {$table_name}_seq;"; + } + + $table_sql .= "\n);"; + $statements[] = $table_sql; + break; + + case 'oracle': + $table_sql .= "\n)"; + $statements[] = $table_sql; + + // do we need to add a sequence and a tigger for auto incrementing columns? + if ($create_sequence) + { + // create the actual sequence + $statements[] = "CREATE SEQUENCE {$table_name}_seq"; + + // the trigger is the mechanism by which we increment the counter + $trigger = "CREATE OR REPLACE TRIGGER t_{$table_name}\n"; + $trigger .= "BEFORE INSERT ON {$table_name}\n"; + $trigger .= "FOR EACH ROW WHEN (\n"; + $trigger .= "\tnew.{$create_sequence} IS NULL OR new.{$create_sequence} = 0\n"; + $trigger .= ")\n"; + $trigger .= "BEGIN\n"; + $trigger .= "\tSELECT {$table_name}_seq.nextval\n"; + $trigger .= "\tINTO :new.{$create_sequence}\n"; + $trigger .= "\tFROM dual;\n"; + $trigger .= "END;"; + + $statements[] = $trigger; + } + break; + + case 'firebird': + if ($create_sequence) + { + $statements[] = "CREATE GENERATOR {$table_name}_gen;"; + $statements[] = "SET GENERATOR {$table_name}_gen TO 0;"; + + $trigger = "CREATE TRIGGER t_$table_name FOR $table_name\n"; + $trigger .= "BEFORE INSERT\nAS\nBEGIN\n"; + $trigger .= "\tNEW.{$create_sequence} = GEN_ID({$table_name}_gen, 1);\nEND;"; + $statements[] = $trigger; + } + break; + } + + // Write Keys + if (isset($table_data['KEYS'])) + { + foreach ($table_data['KEYS'] as $key_name => $key_data) + { + if (!is_array($key_data[1])) + { + $key_data[1] = array($key_data[1]); + } + + $old_return_statements = $this->return_statements; + $this->return_statements = true; + + $key_stmts = ($key_data[0] == 'UNIQUE') ? $this->sql_create_unique_index($table_name, $key_name, $key_data[1]) : $this->sql_create_index($table_name, $key_name, $key_data[1]); + + foreach ($key_stmts as $key_stmt) + { + $statements[] = $key_stmt; + } + + $this->return_statements = $old_return_statements; + } + } + + // Commit Transaction + $statements[] = 'commit'; + + return $this->_sql_run_sql($statements); + } + + /** + * Handle passed database update array. + * Expected structure... + * Key being one of the following + * drop_tables: Drop tables + * add_tables: Add tables + * change_columns: Column changes (only type, not name) + * add_columns: Add columns to a table + * drop_keys: Dropping keys + * drop_columns: Removing/Dropping columns + * add_primary_keys: adding primary keys + * add_unique_index: adding an unique index + * add_index: adding an index (can be column:index_size if you need to provide size) + * + * The values are in this format: + * {TABLE NAME} => array( + * {COLUMN NAME} => array({COLUMN TYPE}, {DEFAULT VALUE}, {OPTIONAL VARIABLES}), + * {KEY/INDEX NAME} => array({COLUMN NAMES}), + * ) + * + * For more information have a look at /develop/create_schema_files.php (only available through SVN) + */ + function perform_schema_changes($schema_changes) + { + if (empty($schema_changes)) + { + return; + } + + $statements = array(); + $sqlite = false; + + // For SQLite we need to perform the schema changes in a much more different way + if ($this->db->sql_layer == 'sqlite' && $this->return_statements) + { + $sqlite_data = array(); + $sqlite = true; + } + + // Drop tables? + if (!empty($schema_changes['drop_tables'])) + { + foreach ($schema_changes['drop_tables'] as $table) + { + // only drop table if it exists + if ($this->sql_table_exists($table)) + { + $result = $this->sql_table_drop($table); + if ($this->return_statements) + { + $statements = array_merge($statements, $result); + } + } + } + } + + // Add tables? + if (!empty($schema_changes['add_tables'])) + { + foreach ($schema_changes['add_tables'] as $table => $table_data) + { + $result = $this->sql_create_table($table, $table_data); + if ($this->return_statements) + { + $statements = array_merge($statements, $result); + } + } + } + + // Change columns? + if (!empty($schema_changes['change_columns'])) + { + foreach ($schema_changes['change_columns'] as $table => $columns) + { + foreach ($columns as $column_name => $column_data) + { + // If the column exists we change it, else we add it ;) + if ($column_exists = $this->sql_column_exists($table, $column_name)) + { + $result = $this->sql_column_change($table, $column_name, $column_data, true); + } + else + { + $result = $this->sql_column_add($table, $column_name, $column_data, true); + } + + if ($sqlite) + { + if ($column_exists) + { + $sqlite_data[$table]['change_columns'][] = $result; + } + else + { + $sqlite_data[$table]['add_columns'][] = $result; + } + } + else if ($this->return_statements) + { + $statements = array_merge($statements, $result); + } + } + } + } + + // Add columns? + if (!empty($schema_changes['add_columns'])) + { + foreach ($schema_changes['add_columns'] as $table => $columns) + { + foreach ($columns as $column_name => $column_data) + { + // Only add the column if it does not exist yet + if ($column_exists = $this->sql_column_exists($table, $column_name)) + { + continue; + // This is commented out here because it can take tremendous time on updates +// $result = $this->sql_column_change($table, $column_name, $column_data, true); + } + else + { + $result = $this->sql_column_add($table, $column_name, $column_data, true); + } + + if ($sqlite) + { + if ($column_exists) + { + continue; +// $sqlite_data[$table]['change_columns'][] = $result; + } + else + { + $sqlite_data[$table]['add_columns'][] = $result; + } + } + else if ($this->return_statements) + { + $statements = array_merge($statements, $result); + } + } + } + } + + // Remove keys? + if (!empty($schema_changes['drop_keys'])) + { + foreach ($schema_changes['drop_keys'] as $table => $indexes) + { + foreach ($indexes as $index_name) + { + if (!$this->sql_index_exists($table, $index_name)) + { + continue; + } + + $result = $this->sql_index_drop($table, $index_name); + + if ($this->return_statements) + { + $statements = array_merge($statements, $result); + } + } + } + } + + // Drop columns? + if (!empty($schema_changes['drop_columns'])) + { + foreach ($schema_changes['drop_columns'] as $table => $columns) + { + foreach ($columns as $column) + { + // Only remove the column if it exists... + if ($this->sql_column_exists($table, $column)) + { + $result = $this->sql_column_remove($table, $column, true); + + if ($sqlite) + { + $sqlite_data[$table]['drop_columns'][] = $result; + } + else if ($this->return_statements) + { + $statements = array_merge($statements, $result); + } + } + } + } + } + + // Add primary keys? + if (!empty($schema_changes['add_primary_keys'])) + { + foreach ($schema_changes['add_primary_keys'] as $table => $columns) + { + $result = $this->sql_create_primary_key($table, $columns, true); + + if ($sqlite) + { + $sqlite_data[$table]['primary_key'] = $result; + } + else if ($this->return_statements) + { + $statements = array_merge($statements, $result); + } + } + } + + // Add unqiue indexes? + if (!empty($schema_changes['add_unique_index'])) + { + foreach ($schema_changes['add_unique_index'] as $table => $index_array) + { + foreach ($index_array as $index_name => $column) + { + if ($this->sql_unique_index_exists($table, $index_name)) + { + continue; + } + + $result = $this->sql_create_unique_index($table, $index_name, $column); + + if ($this->return_statements) + { + $statements = array_merge($statements, $result); + } + } + } + } + + // Add indexes? + if (!empty($schema_changes['add_index'])) + { + foreach ($schema_changes['add_index'] as $table => $index_array) + { + foreach ($index_array as $index_name => $column) + { + if ($this->sql_index_exists($table, $index_name)) + { + continue; + } + + $result = $this->sql_create_index($table, $index_name, $column); + + if ($this->return_statements) + { + $statements = array_merge($statements, $result); + } + } + } + } + + if ($sqlite) + { + foreach ($sqlite_data as $table_name => $sql_schema_changes) + { + // Create temporary table with original data + $statements[] = 'begin'; + + $sql = "SELECT sql + FROM sqlite_master + WHERE type = 'table' + AND name = '{$table_name}' + ORDER BY type DESC, name;"; + $result = $this->db->sql_query($sql); + + if (!$result) + { + continue; + } + + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + // Create a backup table and populate it, destroy the existing one + $statements[] = preg_replace('#CREATE\s+TABLE\s+"?' . $table_name . '"?#i', 'CREATE TEMPORARY TABLE ' . $table_name . '_temp', $row['sql']); + $statements[] = 'INSERT INTO ' . $table_name . '_temp SELECT * FROM ' . $table_name; + $statements[] = 'DROP TABLE ' . $table_name; + + // Get the columns... + preg_match('#\((.*)\)#s', $row['sql'], $matches); + + $plain_table_cols = trim($matches[1]); + $new_table_cols = preg_split('/,(?![\s\w]+\))/m', $plain_table_cols); + $column_list = array(); + + foreach ($new_table_cols as $declaration) + { + $entities = preg_split('#\s+#', trim($declaration)); + if ($entities[0] == 'PRIMARY') + { + continue; + } + $column_list[] = $entities[0]; + } + + // note down the primary key notation because sqlite only supports adding it to the end for the new table + $primary_key = false; + $_new_cols = array(); + + foreach ($new_table_cols as $key => $declaration) + { + $entities = preg_split('#\s+#', trim($declaration)); + if ($entities[0] == 'PRIMARY') + { + $primary_key = $declaration; + continue; + } + $_new_cols[] = $declaration; + } + + $new_table_cols = $_new_cols; + + // First of all... change columns + if (!empty($sql_schema_changes['change_columns'])) + { + foreach ($sql_schema_changes['change_columns'] as $column_sql) + { + foreach ($new_table_cols as $key => $declaration) + { + $entities = preg_split('#\s+#', trim($declaration)); + if (strpos($column_sql, $entities[0] . ' ') === 0) + { + $new_table_cols[$key] = $column_sql; + } + } + } + } + + if (!empty($sql_schema_changes['add_columns'])) + { + foreach ($sql_schema_changes['add_columns'] as $column_sql) + { + $new_table_cols[] = $column_sql; + } + } + + // Now drop them... + if (!empty($sql_schema_changes['drop_columns'])) + { + foreach ($sql_schema_changes['drop_columns'] as $column_name) + { + // Remove from column list... + $new_column_list = array(); + foreach ($column_list as $key => $value) + { + if ($value === $column_name) + { + continue; + } + + $new_column_list[] = $value; + } + + $column_list = $new_column_list; + + // Remove from table... + $_new_cols = array(); + foreach ($new_table_cols as $key => $declaration) + { + $entities = preg_split('#\s+#', trim($declaration)); + if (strpos($column_name . ' ', $entities[0] . ' ') === 0) + { + continue; + } + $_new_cols[] = $declaration; + } + $new_table_cols = $_new_cols; + } + } + + // Primary key... + if (!empty($sql_schema_changes['primary_key'])) + { + $new_table_cols[] = 'PRIMARY KEY (' . implode(', ', $sql_schema_changes['primary_key']) . ')'; + } + // Add a new one or the old primary key + else if ($primary_key !== false) + { + $new_table_cols[] = $primary_key; + } + + $columns = implode(',', $column_list); + + // create a new table and fill it up. destroy the temp one + $statements[] = 'CREATE TABLE ' . $table_name . ' (' . implode(',', $new_table_cols) . ');'; + $statements[] = 'INSERT INTO ' . $table_name . ' (' . $columns . ') SELECT ' . $columns . ' FROM ' . $table_name . '_temp;'; + $statements[] = 'DROP TABLE ' . $table_name . '_temp'; + + $statements[] = 'commit'; + } + } + + if ($this->return_statements) + { + return $statements; + } + } + + /** + * Gets a list of columns of a table. + * + * @param string $table Table name + * + * @return array Array of column names (all lower case) + */ + function sql_list_columns($table) + { + $columns = array(); + + switch ($this->sql_layer) + { + case 'mysql_40': + case 'mysql_41': + $sql = "SHOW COLUMNS FROM $table"; + break; + + // PostgreSQL has a way of doing this in a much simpler way but would + // not allow us to support all versions of PostgreSQL + case 'postgres': + $sql = "SELECT a.attname + FROM pg_class c, pg_attribute a + WHERE c.relname = '{$table}' + AND a.attnum > 0 + AND a.attrelid = c.oid"; + break; + + // same deal with PostgreSQL, we must perform more complex operations than + // we technically could + case 'mssql': + case 'mssqlnative': + $sql = "SELECT c.name + FROM syscolumns c + LEFT JOIN sysobjects o ON c.id = o.id + WHERE o.name = '{$table}'"; + break; + + case 'oracle': + $sql = "SELECT column_name + FROM user_tab_columns + WHERE LOWER(table_name) = '" . strtolower($table) . "'"; + break; + + case 'firebird': + $sql = "SELECT RDB\$FIELD_NAME as FNAME + FROM RDB\$RELATION_FIELDS + WHERE RDB\$RELATION_NAME = '" . strtoupper($table) . "'"; + break; + + case 'sqlite': + $sql = "SELECT sql + FROM sqlite_master + WHERE type = 'table' + AND name = '{$table}'"; + + $result = $this->db->sql_query($sql); + + if (!$result) + { + return false; + } + + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + preg_match('#\((.*)\)#s', $row['sql'], $matches); + + $cols = trim($matches[1]); + $col_array = preg_split('/,(?![\s\w]+\))/m', $cols); + + foreach ($col_array as $declaration) + { + $entities = preg_split('#\s+#', trim($declaration)); + if ($entities[0] == 'PRIMARY') + { + continue; + } + + $column = strtolower($entities[0]); + $columns[$column] = $column; + } + + return $columns; + break; + } + + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $column = strtolower(current($row)); + $columns[$column] = $column; + } + $this->db->sql_freeresult($result); + + return $columns; + } + + /** + * Check whether a specified column exist in a table + * + * @param string $table Table to check + * @param string $column_name Column to check + * + * @return bool True if column exists, false otherwise + */ + function sql_column_exists($table, $column_name) + { + $columns = $this->sql_list_columns($table); + + return isset($columns[$column_name]); + } + + /** + * Check if a specified index exists in table. Does not return PRIMARY KEY and UNIQUE indexes. + * + * @param string $table_name Table to check the index at + * @param string $index_name The index name to check + * + * @return bool True if index exists, else false + */ + function sql_index_exists($table_name, $index_name) + { + if ($this->sql_layer == 'mssql' || $this->sql_layer == 'mssqlnative') + { + $sql = "EXEC sp_statistics '$table_name'"; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + if ($row['TYPE'] == 3) + { + if (strtolower($row['INDEX_NAME']) == strtolower($index_name)) + { + $this->db->sql_freeresult($result); + return true; + } + } + } + $this->db->sql_freeresult($result); + + return false; + } + + switch ($this->sql_layer) + { + case 'firebird': + $sql = "SELECT LOWER(RDB\$INDEX_NAME) as index_name + FROM RDB\$INDICES + WHERE RDB\$RELATION_NAME = '" . strtoupper($table_name) . "' + AND RDB\$UNIQUE_FLAG IS NULL + AND RDB\$FOREIGN_KEY IS NULL"; + $col = 'index_name'; + break; + + case 'postgres': + $sql = "SELECT ic.relname as index_name + FROM pg_class bc, pg_class ic, pg_index i + WHERE (bc.oid = i.indrelid) + AND (ic.oid = i.indexrelid) + AND (bc.relname = '" . $table_name . "') + AND (i.indisunique != 't') + AND (i.indisprimary != 't')"; + $col = 'index_name'; + break; + + case 'mysql_40': + case 'mysql_41': + $sql = 'SHOW KEYS + FROM ' . $table_name; + $col = 'Key_name'; + break; + + case 'oracle': + $sql = "SELECT index_name + FROM user_indexes + WHERE table_name = '" . strtoupper($table_name) . "' + AND generated = 'N' + AND uniqueness = 'NONUNIQUE'"; + $col = 'index_name'; + break; + + case 'sqlite': + $sql = "PRAGMA index_list('" . $table_name . "');"; + $col = 'name'; + break; + } + + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + if (($this->sql_layer == 'mysql_40' || $this->sql_layer == 'mysql_41') && !$row['Non_unique']) + { + continue; + } + + // These DBMS prefix index name with the table name + switch ($this->sql_layer) + { + case 'firebird': + case 'oracle': + case 'postgres': + case 'sqlite': + $row[$col] = substr($row[$col], strlen($table_name) + 1); + break; + } + + if (strtolower($row[$col]) == strtolower($index_name)) + { + $this->db->sql_freeresult($result); + return true; + } + } + $this->db->sql_freeresult($result); + + return false; + } + + /** + * Check if a specified index exists in table. Does not return PRIMARY KEY and UNIQUE indexes. + * + * @param string $table_name Table to check the index at + * @param string $index_name The index name to check + * + * @return bool True if index exists, else false + */ + function sql_unique_index_exists($table_name, $index_name) + { + if ($this->sql_layer == 'mssql' || $this->sql_layer == 'mssqlnative') + { + $sql = "EXEC sp_statistics '$table_name'"; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + // Usually NON_UNIQUE is the column we want to check, but we allow for both + if ($row['TYPE'] == 3) + { + if (strtolower($row['INDEX_NAME']) == strtolower($index_name)) + { + $this->db->sql_freeresult($result); + return true; + } + } + } + $this->db->sql_freeresult($result); + return false; + } + + switch ($this->sql_layer) + { + case 'firebird': + $sql = "SELECT LOWER(RDB\$INDEX_NAME) as index_name + FROM RDB\$INDICES + WHERE RDB\$RELATION_NAME = '" . strtoupper($table_name) . "' + AND RDB\$UNIQUE_FLAG IS NOT NULL + AND RDB\$FOREIGN_KEY IS NULL"; + $col = 'index_name'; + break; + + case 'postgres': + $sql = "SELECT ic.relname as index_name, i.indisunique + FROM pg_class bc, pg_class ic, pg_index i + WHERE (bc.oid = i.indrelid) + AND (ic.oid = i.indexrelid) + AND (bc.relname = '" . $table_name . "') + AND (i.indisprimary != 't')"; + $col = 'index_name'; + break; + + case 'mysql_40': + case 'mysql_41': + $sql = 'SHOW KEYS + FROM ' . $table_name; + $col = 'Key_name'; + break; + + case 'oracle': + $sql = "SELECT index_name, table_owner + FROM user_indexes + WHERE table_name = '" . strtoupper($table_name) . "' + AND generated = 'N' + AND uniqueness = 'UNIQUE'"; + $col = 'index_name'; + break; + + case 'sqlite': + $sql = "PRAGMA index_list('" . $table_name . "');"; + $col = 'name'; + break; + } + + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + if (($this->sql_layer == 'mysql_40' || $this->sql_layer == 'mysql_41') && ($row['Non_unique'] || $row[$col] == 'PRIMARY')) + { + continue; + } + + if ($this->sql_layer == 'sqlite' && !$row['unique']) + { + continue; + } + + if ($this->sql_layer == 'postgres' && $row['indisunique'] != 't') + { + continue; + } + + // These DBMS prefix index name with the table name + switch ($this->sql_layer) + { + case 'oracle': + // Two cases here... prefixed with U_[table_owner] and not prefixed with table_name + if (strpos($row[$col], 'U_') === 0) + { + $row[$col] = substr($row[$col], strlen('U_' . $row['table_owner']) + 1); + } + else if (strpos($row[$col], strtoupper($table_name)) === 0) + { + $row[$col] = substr($row[$col], strlen($table_name) + 1); + } + break; + + case 'firebird': + case 'postgres': + case 'sqlite': + $row[$col] = substr($row[$col], strlen($table_name) + 1); + break; + } + + if (strtolower($row[$col]) == strtolower($index_name)) + { + $this->db->sql_freeresult($result); + return true; + } + } + $this->db->sql_freeresult($result); + + return false; + } + + /** + * Private method for performing sql statements (either execute them or return them) + * @access private + */ + function _sql_run_sql($statements) + { + if ($this->return_statements) + { + return $statements; + } + + // We could add error handling here... + foreach ($statements as $sql) + { + if ($sql === 'begin') + { + $this->db->sql_transaction('begin'); + } + else if ($sql === 'commit') + { + $this->db->sql_transaction('commit'); + } + else + { + $this->db->sql_query($sql); + } + } + + return true; + } + + /** + * Function to prepare some column information for better usage + * @access private + */ + function sql_prepare_column_data($table_name, $column_name, $column_data) + { + if (strlen($column_name) > 30) + { + trigger_error("Column name '$column_name' on table '$table_name' is too long. The maximum is 30 characters.", E_USER_ERROR); + } + + // Get type + if (strpos($column_data[0], ':') !== false) + { + list($orig_column_type, $column_length) = explode(':', $column_data[0]); + if (!is_array($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':'])) + { + $column_type = sprintf($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':'], $column_length); + } + else + { + if (isset($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['rule'])) + { + switch ($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['rule'][0]) + { + case 'div': + $column_length /= $this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['rule'][1]; + $column_length = ceil($column_length); + $column_type = sprintf($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':'][0], $column_length); + break; + } + } + + if (isset($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'])) + { + switch ($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'][0]) + { + case 'mult': + $column_length *= $this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'][1]; + if ($column_length > $this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'][2]) + { + $column_type = $this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'][3]; + } + else + { + $column_type = sprintf($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':'][0], $column_length); + } + break; + } + } + } + $orig_column_type .= ':'; + } + else + { + $orig_column_type = $column_data[0]; + $column_type = $this->dbms_type_map[$this->sql_layer][$column_data[0]]; + } + + // Adjust default value if db-dependent specified + if (is_array($column_data[1])) + { + $column_data[1] = (isset($column_data[1][$this->sql_layer])) ? $column_data[1][$this->sql_layer] : $column_data[1]['default']; + } + + $sql = ''; + + $return_array = array(); + + switch ($this->sql_layer) + { + case 'firebird': + $sql .= " {$column_type} "; + $return_array['column_type_sql_type'] = " {$column_type} "; + + if (!is_null($column_data[1])) + { + $sql .= 'DEFAULT ' . ((is_numeric($column_data[1])) ? $column_data[1] : "'{$column_data[1]}'") . ' '; + $return_array['column_type_sql_default'] = ((is_numeric($column_data[1])) ? $column_data[1] : "'{$column_data[1]}'") . ' '; + } + + $sql .= 'NOT NULL'; + + // This is a UNICODE column and thus should be given it's fair share + if (preg_match('/^X?STEXT_UNI|VCHAR_(CI|UNI:?)/', $column_data[0])) + { + $sql .= ' COLLATE UNICODE'; + } + + $return_array['auto_increment'] = false; + if (isset($column_data[2]) && $column_data[2] == 'auto_increment') + { + $return_array['auto_increment'] = true; + } + + break; + + case 'mssql': + case 'mssqlnative': + $sql .= " {$column_type} "; + $sql_default = " {$column_type} "; + + // For adding columns we need the default definition + if (!is_null($column_data[1])) + { + // For hexadecimal values do not use single quotes + if (strpos($column_data[1], '0x') === 0) + { + $return_array['default'] = 'DEFAULT (' . $column_data[1] . ') '; + $sql_default .= $return_array['default']; + } + else + { + $return_array['default'] = 'DEFAULT (' . ((is_numeric($column_data[1])) ? $column_data[1] : "'{$column_data[1]}'") . ') '; + $sql_default .= $return_array['default']; + } + } + + if (isset($column_data[2]) && $column_data[2] == 'auto_increment') + { +// $sql .= 'IDENTITY (1, 1) '; + $sql_default .= 'IDENTITY (1, 1) '; + } + + $return_array['textimage'] = $column_type === '[text]'; + + $sql .= 'NOT NULL'; + $sql_default .= 'NOT NULL'; + + $return_array['column_type_sql_default'] = $sql_default; + + break; + + case 'mysql_40': + case 'mysql_41': + $sql .= " {$column_type} "; + + // For hexadecimal values do not use single quotes + if (!is_null($column_data[1]) && substr($column_type, -4) !== 'text' && substr($column_type, -4) !== 'blob') + { + $sql .= (strpos($column_data[1], '0x') === 0) ? "DEFAULT {$column_data[1]} " : "DEFAULT '{$column_data[1]}' "; + } + $sql .= 'NOT NULL'; + + if (isset($column_data[2])) + { + if ($column_data[2] == 'auto_increment') + { + $sql .= ' auto_increment'; + } + else if ($this->sql_layer === 'mysql_41' && $column_data[2] == 'true_sort') + { + $sql .= ' COLLATE utf8_unicode_ci'; + } + } + + break; + + case 'oracle': + $sql .= " {$column_type} "; + $sql .= (!is_null($column_data[1])) ? "DEFAULT '{$column_data[1]}' " : ''; + + // In Oracle empty strings ('') are treated as NULL. + // Therefore in oracle we allow NULL's for all DEFAULT '' entries + // Oracle does not like setting NOT NULL on a column that is already NOT NULL (this happens only on number fields) + if (!preg_match('/number/i', $column_type)) + { + $sql .= ($column_data[1] === '') ? '' : 'NOT NULL'; + } + + $return_array['auto_increment'] = false; + if (isset($column_data[2]) && $column_data[2] == 'auto_increment') + { + $return_array['auto_increment'] = true; + } + + break; + + case 'postgres': + $return_array['column_type'] = $column_type; + + $sql .= " {$column_type} "; + + $return_array['auto_increment'] = false; + if (isset($column_data[2]) && $column_data[2] == 'auto_increment') + { + $default_val = "nextval('{$table_name}_seq')"; + $return_array['auto_increment'] = true; + } + else if (!is_null($column_data[1])) + { + $default_val = "'" . $column_data[1] . "'"; + $return_array['null'] = 'NOT NULL'; + $sql .= 'NOT NULL '; + } + + $return_array['default'] = $default_val; + + $sql .= "DEFAULT {$default_val}"; + + // Unsigned? Then add a CHECK contraint + if (in_array($orig_column_type, $this->unsigned_types)) + { + $return_array['constraint'] = "CHECK ({$column_name} >= 0)"; + $sql .= " CHECK ({$column_name} >= 0)"; + } + + break; + + case 'sqlite': + $return_array['primary_key_set'] = false; + if (isset($column_data[2]) && $column_data[2] == 'auto_increment') + { + $sql .= ' INTEGER PRIMARY KEY'; + $return_array['primary_key_set'] = true; + } + else + { + $sql .= ' ' . $column_type; + } + + $sql .= ' NOT NULL '; + $sql .= (!is_null($column_data[1])) ? "DEFAULT '{$column_data[1]}'" : ''; + + break; + } + + $return_array['column_type_sql'] = $sql; + + return $return_array; + } + + /** + * Add new column + */ + function sql_column_add($table_name, $column_name, $column_data, $inline = false) + { + $column_data = $this->sql_prepare_column_data($table_name, $column_name, $column_data); + $statements = array(); + + switch ($this->sql_layer) + { + case 'firebird': + // Does not support AFTER statement, only POSITION (and there you need the column position) + $statements[] = 'ALTER TABLE ' . $table_name . ' ADD "' . strtoupper($column_name) . '" ' . $column_data['column_type_sql']; + break; + + case 'mssql': + case 'mssqlnative': + // Does not support AFTER, only through temporary table + $statements[] = 'ALTER TABLE [' . $table_name . '] ADD [' . $column_name . '] ' . $column_data['column_type_sql_default']; + break; + + case 'mysql_40': + case 'mysql_41': + $after = (!empty($column_data['after'])) ? ' AFTER ' . $column_data['after'] : ''; + $statements[] = 'ALTER TABLE `' . $table_name . '` ADD COLUMN `' . $column_name . '` ' . $column_data['column_type_sql'] . $after; + break; + + case 'oracle': + // Does not support AFTER, only through temporary table + $statements[] = 'ALTER TABLE ' . $table_name . ' ADD ' . $column_name . ' ' . $column_data['column_type_sql']; + break; + + case 'postgres': + // Does not support AFTER, only through temporary table + if (version_compare($this->db->sql_server_info(true), '8.0', '>=')) + { + $statements[] = 'ALTER TABLE ' . $table_name . ' ADD COLUMN "' . $column_name . '" ' . $column_data['column_type_sql']; + } + else + { + // old versions cannot add columns with default and null information + $statements[] = 'ALTER TABLE ' . $table_name . ' ADD COLUMN "' . $column_name . '" ' . $column_data['column_type'] . ' ' . $column_data['constraint']; + + if (isset($column_data['null'])) + { + if ($column_data['null'] == 'NOT NULL') + { + $statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN ' . $column_name . ' SET NOT NULL'; + } + } + + if (isset($column_data['default'])) + { + $statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN ' . $column_name . ' SET DEFAULT ' . $column_data['default']; + } + } + + break; + + case 'sqlite': + + if ($inline && $this->return_statements) + { + return $column_name . ' ' . $column_data['column_type_sql']; + } + + if (version_compare(sqlite_libversion(), '3.0') == -1) + { + $sql = "SELECT sql + FROM sqlite_master + WHERE type = 'table' + AND name = '{$table_name}' + ORDER BY type DESC, name;"; + $result = $this->db->sql_query($sql); + + if (!$result) + { + break; + } + + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + $statements[] = 'begin'; + + // Create a backup table and populate it, destroy the existing one + $statements[] = preg_replace('#CREATE\s+TABLE\s+"?' . $table_name . '"?#i', 'CREATE TEMPORARY TABLE ' . $table_name . '_temp', $row['sql']); + $statements[] = 'INSERT INTO ' . $table_name . '_temp SELECT * FROM ' . $table_name; + $statements[] = 'DROP TABLE ' . $table_name; + + preg_match('#\((.*)\)#s', $row['sql'], $matches); + + $new_table_cols = trim($matches[1]); + $old_table_cols = preg_split('/,(?![\s\w]+\))/m', $new_table_cols); + $column_list = array(); + + foreach ($old_table_cols as $declaration) + { + $entities = preg_split('#\s+#', trim($declaration)); + if ($entities[0] == 'PRIMARY') + { + continue; + } + $column_list[] = $entities[0]; + } + + $columns = implode(',', $column_list); + + $new_table_cols = $column_name . ' ' . $column_data['column_type_sql'] . ',' . $new_table_cols; + + // create a new table and fill it up. destroy the temp one + $statements[] = 'CREATE TABLE ' . $table_name . ' (' . $new_table_cols . ');'; + $statements[] = 'INSERT INTO ' . $table_name . ' (' . $columns . ') SELECT ' . $columns . ' FROM ' . $table_name . '_temp;'; + $statements[] = 'DROP TABLE ' . $table_name . '_temp'; + + $statements[] = 'commit'; + } + else + { + $statements[] = 'ALTER TABLE ' . $table_name . ' ADD ' . $column_name . ' [' . $column_data['column_type_sql'] . ']'; + } + break; + } + + return $this->_sql_run_sql($statements); + } + + /** + * Drop column + */ + function sql_column_remove($table_name, $column_name, $inline = false) + { + $statements = array(); + + switch ($this->sql_layer) + { + case 'firebird': + $statements[] = 'ALTER TABLE ' . $table_name . ' DROP "' . strtoupper($column_name) . '"'; + break; + + case 'mssql': + case 'mssqlnative': + // remove default cosntraints first + // http://msdn.microsoft.com/en-us/library/aa175912%28v=sql.80%29.aspx + $statements[] = "DECLARE @drop_default_name VARCHAR(100), @cmd VARCHAR(1000) + SET @drop_default_name = + (SELECT so.name FROM sysobjects so + JOIN sysconstraints sc ON so.id = sc.constid + WHERE object_name(so.parent_obj) = '{$table_name}' + AND so.xtype = 'D' + AND sc.colid = (SELECT colid FROM syscolumns + WHERE id = object_id('{$table_name}') + AND name = '{$column_name}')) + IF @drop_default_name <> '' + BEGIN + SET @cmd = 'ALTER TABLE [{$table_name}] DROP CONSTRAINT [' + @drop_default_name + ']' + EXEC(@cmd) + END"; + $statements[] = 'ALTER TABLE [' . $table_name . '] DROP COLUMN [' . $column_name . ']'; + break; + + case 'mysql_40': + case 'mysql_41': + $statements[] = 'ALTER TABLE `' . $table_name . '` DROP COLUMN `' . $column_name . '`'; + break; + + case 'oracle': + $statements[] = 'ALTER TABLE ' . $table_name . ' DROP COLUMN ' . $column_name; + break; + + case 'postgres': + $statements[] = 'ALTER TABLE ' . $table_name . ' DROP COLUMN "' . $column_name . '"'; + break; + + case 'sqlite': + + if ($inline && $this->return_statements) + { + return $column_name; + } + + if (version_compare(sqlite_libversion(), '3.0') == -1) + { + $sql = "SELECT sql + FROM sqlite_master + WHERE type = 'table' + AND name = '{$table_name}' + ORDER BY type DESC, name;"; + $result = $this->db->sql_query($sql); + + if (!$result) + { + break; + } + + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + $statements[] = 'begin'; + + // Create a backup table and populate it, destroy the existing one + $statements[] = preg_replace('#CREATE\s+TABLE\s+"?' . $table_name . '"?#i', 'CREATE TEMPORARY TABLE ' . $table_name . '_temp', $row['sql']); + $statements[] = 'INSERT INTO ' . $table_name . '_temp SELECT * FROM ' . $table_name; + $statements[] = 'DROP TABLE ' . $table_name; + + preg_match('#\((.*)\)#s', $row['sql'], $matches); + + $new_table_cols = trim($matches[1]); + $old_table_cols = preg_split('/,(?![\s\w]+\))/m', $new_table_cols); + $column_list = array(); + + foreach ($old_table_cols as $declaration) + { + $entities = preg_split('#\s+#', trim($declaration)); + if ($entities[0] == 'PRIMARY' || $entities[0] === $column_name) + { + continue; + } + $column_list[] = $entities[0]; + } + + $columns = implode(',', $column_list); + + $new_table_cols = preg_replace('/' . $column_name . '[^,]+(?:,|$)/m', '', $new_table_cols); + + // create a new table and fill it up. destroy the temp one + $statements[] = 'CREATE TABLE ' . $table_name . ' (' . $new_table_cols . ');'; + $statements[] = 'INSERT INTO ' . $table_name . ' (' . $columns . ') SELECT ' . $columns . ' FROM ' . $table_name . '_temp;'; + $statements[] = 'DROP TABLE ' . $table_name . '_temp'; + + $statements[] = 'commit'; + } + else + { + $statements[] = 'ALTER TABLE ' . $table_name . ' DROP COLUMN ' . $column_name; + } + break; + } + + return $this->_sql_run_sql($statements); + } + + /** + * Drop Index + */ + function sql_index_drop($table_name, $index_name) + { + $statements = array(); + + switch ($this->sql_layer) + { + case 'mssql': + case 'mssqlnative': + $statements[] = 'DROP INDEX ' . $table_name . '.' . $index_name; + break; + + case 'mysql_40': + case 'mysql_41': + $statements[] = 'DROP INDEX ' . $index_name . ' ON ' . $table_name; + break; + + case 'firebird': + case 'oracle': + case 'postgres': + case 'sqlite': + $statements[] = 'DROP INDEX ' . $table_name . '_' . $index_name; + break; + } + + return $this->_sql_run_sql($statements); + } + + /** + * Drop Table + */ + function sql_table_drop($table_name) + { + $statements = array(); + + if (!$this->sql_table_exists($table_name)) + { + return $this->_sql_run_sql($statements); + } + + // the most basic operation, get rid of the table + $statements[] = 'DROP TABLE ' . $table_name; + + switch ($this->sql_layer) + { + case 'firebird': + $sql = 'SELECT RDB$GENERATOR_NAME as gen + FROM RDB$GENERATORS + WHERE RDB$SYSTEM_FLAG = 0 + AND RDB$GENERATOR_NAME = \'' . strtoupper($table_name) . "_GEN'"; + $result = $this->db->sql_query($sql); + + // does a generator exist? + if ($row = $this->db->sql_fetchrow($result)) + { + $statements[] = "DROP GENERATOR {$row['gen']};"; + } + $this->db->sql_freeresult($result); + break; + + case 'oracle': + $sql = 'SELECT A.REFERENCED_NAME + FROM USER_DEPENDENCIES A, USER_TRIGGERS B + WHERE A.REFERENCED_TYPE = \'SEQUENCE\' + AND A.NAME = B.TRIGGER_NAME + AND B.TABLE_NAME = \'' . strtoupper($table_name) . "'"; + $result = $this->db->sql_query($sql); + + // any sequences ref'd to this table's triggers? + while ($row = $this->db->sql_fetchrow($result)) + { + $statements[] = "DROP SEQUENCE {$row['referenced_name']}"; + } + $this->db->sql_freeresult($result); + break; + + case 'postgres': + // PGSQL does not "tightly" bind sequences and tables, we must guess... + $sql = "SELECT relname + FROM pg_class + WHERE relkind = 'S' + AND relname = '{$table_name}_seq'"; + $result = $this->db->sql_query($sql); + + // We don't even care about storing the results. We already know the answer if we get rows back. + if ($this->db->sql_fetchrow($result)) + { + $statements[] = "DROP SEQUENCE {$table_name}_seq;\n"; + } + $this->db->sql_freeresult($result); + break; + } + + return $this->_sql_run_sql($statements); + } + + /** + * Add primary key + */ + function sql_create_primary_key($table_name, $column, $inline = false) + { + $statements = array(); + + switch ($this->sql_layer) + { + case 'firebird': + case 'postgres': + case 'mysql_40': + case 'mysql_41': + $statements[] = 'ALTER TABLE ' . $table_name . ' ADD PRIMARY KEY (' . implode(', ', $column) . ')'; + break; + + case 'mssql': + case 'mssqlnative': + $sql = "ALTER TABLE [{$table_name}] WITH NOCHECK ADD "; + $sql .= "CONSTRAINT [PK_{$table_name}] PRIMARY KEY CLUSTERED ("; + $sql .= '[' . implode("],\n\t\t[", $column) . ']'; + $sql .= ') ON [PRIMARY]'; + + $statements[] = $sql; + break; + + case 'oracle': + $statements[] = 'ALTER TABLE ' . $table_name . 'add CONSTRAINT pk_' . $table_name . ' PRIMARY KEY (' . implode(', ', $column) . ')'; + break; + + case 'sqlite': + + if ($inline && $this->return_statements) + { + return $column; + } + + $sql = "SELECT sql + FROM sqlite_master + WHERE type = 'table' + AND name = '{$table_name}' + ORDER BY type DESC, name;"; + $result = $this->db->sql_query($sql); + + if (!$result) + { + break; + } + + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + $statements[] = 'begin'; + + // Create a backup table and populate it, destroy the existing one + $statements[] = preg_replace('#CREATE\s+TABLE\s+"?' . $table_name . '"?#i', 'CREATE TEMPORARY TABLE ' . $table_name . '_temp', $row['sql']); + $statements[] = 'INSERT INTO ' . $table_name . '_temp SELECT * FROM ' . $table_name; + $statements[] = 'DROP TABLE ' . $table_name; + + preg_match('#\((.*)\)#s', $row['sql'], $matches); + + $new_table_cols = trim($matches[1]); + $old_table_cols = preg_split('/,(?![\s\w]+\))/m', $new_table_cols); + $column_list = array(); + + foreach ($old_table_cols as $declaration) + { + $entities = preg_split('#\s+#', trim($declaration)); + if ($entities[0] == 'PRIMARY') + { + continue; + } + $column_list[] = $entities[0]; + } + + $columns = implode(',', $column_list); + + // create a new table and fill it up. destroy the temp one + $statements[] = 'CREATE TABLE ' . $table_name . ' (' . $new_table_cols . ', PRIMARY KEY (' . implode(', ', $column) . '));'; + $statements[] = 'INSERT INTO ' . $table_name . ' (' . $columns . ') SELECT ' . $columns . ' FROM ' . $table_name . '_temp;'; + $statements[] = 'DROP TABLE ' . $table_name . '_temp'; + + $statements[] = 'commit'; + break; + } + + return $this->_sql_run_sql($statements); + } + + /** + * Add unique index + */ + function sql_create_unique_index($table_name, $index_name, $column) + { + $statements = array(); + + $table_prefix = substr(CONFIG_TABLE, 0, -6); // strlen(config) + if (strlen($table_name . $index_name) - strlen($table_prefix) > 24) + { + $max_length = strlen($table_prefix) + 24; + trigger_error("Index name '{$table_name}_$index_name' on table '$table_name' is too long. The maximum is $max_length characters.", E_USER_ERROR); + } + + switch ($this->sql_layer) + { + case 'firebird': + case 'postgres': + case 'oracle': + case 'sqlite': + $statements[] = 'CREATE UNIQUE INDEX ' . $table_name . '_' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ')'; + break; + + case 'mysql_40': + case 'mysql_41': + $statements[] = 'ALTER TABLE ' . $table_name . ' ADD UNIQUE INDEX ' . $index_name . '(' . implode(', ', $column) . ')'; + break; + + case 'mssql': + case 'mssqlnative': + $statements[] = 'CREATE UNIQUE INDEX ' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ') ON [PRIMARY]'; + break; + } + + return $this->_sql_run_sql($statements); + } + + /** + * Add index + */ + function sql_create_index($table_name, $index_name, $column) + { + $statements = array(); + + $table_prefix = substr(CONFIG_TABLE, 0, -6); // strlen(config) + if (strlen($table_name . $index_name) - strlen($table_prefix) > 24) + { + $max_length = strlen($table_prefix) + 24; + trigger_error("Index name '{$table_name}_$index_name' on table '$table_name' is too long. The maximum is $max_length characters.", E_USER_ERROR); + } + + // remove index length unless MySQL4 + if ('mysql_40' != $this->sql_layer) + { + $column = preg_replace('#:.*$#', '', $column); + } + + switch ($this->sql_layer) + { + case 'firebird': + case 'postgres': + case 'oracle': + case 'sqlite': + $statements[] = 'CREATE INDEX ' . $table_name . '_' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ')'; + break; + + case 'mysql_40': + // add index size to definition as required by MySQL4 + foreach ($column as $i => $col) + { + if (false !== strpos($col, ':')) + { + list($col, $index_size) = explode(':', $col); + $column[$i] = "$col($index_size)"; + } + } + // no break + case 'mysql_41': + $statements[] = 'ALTER TABLE ' . $table_name . ' ADD INDEX ' . $index_name . '(' . implode(', ', $column) . ')'; + break; + + case 'mssql': + case 'mssqlnative': + $statements[] = 'CREATE INDEX ' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ') ON [PRIMARY]'; + break; + } + + return $this->_sql_run_sql($statements); + } + + /** + * List all of the indices that belong to a table, + * does not count: + * * UNIQUE indices + * * PRIMARY keys + */ + function sql_list_index($table_name) + { + $index_array = array(); + + if ($this->sql_layer == 'mssql' || $this->sql_layer == 'mssqlnative') + { + $sql = "EXEC sp_statistics '$table_name'"; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + if ($row['TYPE'] == 3) + { + $index_array[] = $row['INDEX_NAME']; + } + } + $this->db->sql_freeresult($result); + } + else + { + switch ($this->sql_layer) + { + case 'firebird': + $sql = "SELECT LOWER(RDB\$INDEX_NAME) as index_name + FROM RDB\$INDICES + WHERE RDB\$RELATION_NAME = '" . strtoupper($table_name) . "' + AND RDB\$UNIQUE_FLAG IS NULL + AND RDB\$FOREIGN_KEY IS NULL"; + $col = 'index_name'; + break; + + case 'postgres': + $sql = "SELECT ic.relname as index_name + FROM pg_class bc, pg_class ic, pg_index i + WHERE (bc.oid = i.indrelid) + AND (ic.oid = i.indexrelid) + AND (bc.relname = '" . $table_name . "') + AND (i.indisunique != 't') + AND (i.indisprimary != 't')"; + $col = 'index_name'; + break; + + case 'mysql_40': + case 'mysql_41': + $sql = 'SHOW KEYS + FROM ' . $table_name; + $col = 'Key_name'; + break; + + case 'oracle': + $sql = "SELECT index_name + FROM user_indexes + WHERE table_name = '" . strtoupper($table_name) . "' + AND generated = 'N' + AND uniqueness = 'NONUNIQUE'"; + $col = 'index_name'; + break; + + case 'sqlite': + $sql = "PRAGMA index_info('" . $table_name . "');"; + $col = 'name'; + break; + } + + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + if (($this->sql_layer == 'mysql_40' || $this->sql_layer == 'mysql_41') && !$row['Non_unique']) + { + continue; + } + + switch ($this->sql_layer) + { + case 'firebird': + case 'oracle': + case 'postgres': + case 'sqlite': + $row[$col] = substr($row[$col], strlen($table_name) + 1); + break; + } + + $index_array[] = $row[$col]; + } + $this->db->sql_freeresult($result); + } + + return array_map('strtolower', $index_array); + } + + /** + * Change column type (not name!) + */ + function sql_column_change($table_name, $column_name, $column_data, $inline = false) + { + $column_data = $this->sql_prepare_column_data($table_name, $column_name, $column_data); + $statements = array(); + + switch ($this->sql_layer) + { + case 'firebird': + // Change type... + if (!empty($column_data['column_type_sql_default'])) + { + $statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN "' . strtoupper($column_name) . '" TYPE ' . ' ' . $column_data['column_type_sql_type']; + $statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN "' . strtoupper($column_name) . '" SET DEFAULT ' . ' ' . $column_data['column_type_sql_default']; + } + else + { + // TODO: try to change pkey without removing trigger, generator or constraints. ATM this query may fail. + $statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN "' . strtoupper($column_name) . '" TYPE ' . ' ' . $column_data['column_type_sql_type']; + } + break; + + case 'mssql': + case 'mssqlnative': + $statements[] = 'ALTER TABLE [' . $table_name . '] ALTER COLUMN [' . $column_name . '] ' . $column_data['column_type_sql']; + + if (!empty($column_data['default'])) + { + // Using TRANSACT-SQL for this statement because we do not want to have colliding data if statements are executed at a later stage + $statements[] = "DECLARE @drop_default_name VARCHAR(100), @cmd VARCHAR(1000) + SET @drop_default_name = + (SELECT so.name FROM sysobjects so + JOIN sysconstraints sc ON so.id = sc.constid + WHERE object_name(so.parent_obj) = '{$table_name}' + AND so.xtype = 'D' + AND sc.colid = (SELECT colid FROM syscolumns + WHERE id = object_id('{$table_name}') + AND name = '{$column_name}')) + IF @drop_default_name <> '' + BEGIN + SET @cmd = 'ALTER TABLE [{$table_name}] DROP CONSTRAINT [' + @drop_default_name + ']' + EXEC(@cmd) + END + SET @cmd = 'ALTER TABLE [{$table_name}] ADD CONSTRAINT [DF_{$table_name}_{$column_name}_1] {$column_data['default']} FOR [{$column_name}]' + EXEC(@cmd)"; + } + break; + + case 'mysql_40': + case 'mysql_41': + $statements[] = 'ALTER TABLE `' . $table_name . '` CHANGE `' . $column_name . '` `' . $column_name . '` ' . $column_data['column_type_sql']; + break; + + case 'oracle': + $statements[] = 'ALTER TABLE ' . $table_name . ' MODIFY ' . $column_name . ' ' . $column_data['column_type_sql']; + break; + + case 'postgres': + $sql = 'ALTER TABLE ' . $table_name . ' '; + + $sql_array = array(); + $sql_array[] = 'ALTER COLUMN ' . $column_name . ' TYPE ' . $column_data['column_type']; + + if (isset($column_data['null'])) + { + if ($column_data['null'] == 'NOT NULL') + { + $sql_array[] = 'ALTER COLUMN ' . $column_name . ' SET NOT NULL'; + } + else if ($column_data['null'] == 'NULL') + { + $sql_array[] = 'ALTER COLUMN ' . $column_name . ' DROP NOT NULL'; + } + } + + if (isset($column_data['default'])) + { + $sql_array[] = 'ALTER COLUMN ' . $column_name . ' SET DEFAULT ' . $column_data['default']; + } + + // we don't want to double up on constraints if we change different number data types + if (isset($column_data['constraint'])) + { + $constraint_sql = "SELECT consrc as constraint_data + FROM pg_constraint, pg_class bc + WHERE conrelid = bc.oid + AND bc.relname = '{$table_name}' + AND NOT EXISTS ( + SELECT * + FROM pg_constraint as c, pg_inherits as i + WHERE i.inhrelid = pg_constraint.conrelid + AND c.conname = pg_constraint.conname + AND c.consrc = pg_constraint.consrc + AND c.conrelid = i.inhparent + )"; + + $constraint_exists = false; + + $result = $this->db->sql_query($constraint_sql); + while ($row = $this->db->sql_fetchrow($result)) + { + if (trim($row['constraint_data']) == trim($column_data['constraint'])) + { + $constraint_exists = true; + break; + } + } + $this->db->sql_freeresult($result); + + if (!$constraint_exists) + { + $sql_array[] = 'ADD ' . $column_data['constraint']; + } + } + + $sql .= implode(', ', $sql_array); + + $statements[] = $sql; + break; + + case 'sqlite': + + if ($inline && $this->return_statements) + { + return $column_name . ' ' . $column_data['column_type_sql']; + } + + $sql = "SELECT sql + FROM sqlite_master + WHERE type = 'table' + AND name = '{$table_name}' + ORDER BY type DESC, name;"; + $result = $this->db->sql_query($sql); + + if (!$result) + { + break; + } + + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + $statements[] = 'begin'; + + // Create a temp table and populate it, destroy the existing one + $statements[] = preg_replace('#CREATE\s+TABLE\s+"?' . $table_name . '"?#i', 'CREATE TEMPORARY TABLE ' . $table_name . '_temp', $row['sql']); + $statements[] = 'INSERT INTO ' . $table_name . '_temp SELECT * FROM ' . $table_name; + $statements[] = 'DROP TABLE ' . $table_name; + + preg_match('#\((.*)\)#s', $row['sql'], $matches); + + $new_table_cols = trim($matches[1]); + $old_table_cols = preg_split('/,(?![\s\w]+\))/m', $new_table_cols); + $column_list = array(); + + foreach ($old_table_cols as $key => $declaration) + { + $entities = preg_split('#\s+#', trim($declaration)); + $column_list[] = $entities[0]; + if ($entities[0] == $column_name) + { + $old_table_cols[$key] = $column_name . ' ' . $column_data['column_type_sql']; + } + } + + $columns = implode(',', $column_list); + + // create a new table and fill it up. destroy the temp one + $statements[] = 'CREATE TABLE ' . $table_name . ' (' . implode(',', $old_table_cols) . ');'; + $statements[] = 'INSERT INTO ' . $table_name . ' (' . $columns . ') SELECT ' . $columns . ' FROM ' . $table_name . '_temp;'; + $statements[] = 'DROP TABLE ' . $table_name . '_temp'; + + $statements[] = 'commit'; + + break; + } + + return $this->_sql_run_sql($statements); + } +} diff --git a/phpBB/includes/functions_install.php b/phpBB/includes/functions_install.php index 8978e3fadd..bd0ffaaf00 100644 --- a/phpBB/includes/functions_install.php +++ b/phpBB/includes/functions_install.php @@ -184,12 +184,6 @@ function dbms_select($default = '', $only_20x_options = false) */ function get_tables(&$db) { - if (!class_exists('phpbb_db_tools')) - { - global $phpbb_root_path, $phpEx; - require($phpbb_root_path . 'includes/db/db_tools.' . $phpEx); - } - $db_tools = new phpbb_db_tools($db); return $db_tools->sql_list_tables(); diff --git a/phpBB/phpbb/search/fulltext_sphinx.php b/phpBB/phpbb/search/fulltext_sphinx.php index 2f7b236c78..4f3f852664 100644 --- a/phpBB/phpbb/search/fulltext_sphinx.php +++ b/phpBB/phpbb/search/fulltext_sphinx.php @@ -135,11 +135,6 @@ class phpbb_search_fulltext_sphinx $this->db = $db; $this->auth = $auth; - if (!class_exists('phpbb_db_tools')) - { - require($this->phpbb_root_path . 'includes/db/db_tools.' . $this->php_ext); - } - // Initialize phpbb_db_tools object $this->db_tools = new phpbb_db_tools($this->db); -- cgit v1.2.1 From 131194d216f85dffe4fb9d8fd64eb15248fd374b Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Sun, 14 Jul 2013 10:12:49 -0400 Subject: [ticket/11696] Rename constructor to __construct() PHPBB3-11696 --- phpBB/includes/db/tools.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB') diff --git a/phpBB/includes/db/tools.php b/phpBB/includes/db/tools.php index 983cdc18ea..492284ffcd 100644 --- a/phpBB/includes/db/tools.php +++ b/phpBB/includes/db/tools.php @@ -303,7 +303,7 @@ class phpbb_db_tools * @param phpbb_db_driver $db Database connection * @param bool $return_statements True if only statements should be returned and no SQL being executed */ - function phpbb_db_tools(phpbb_db_driver $db, $return_statements = false) + public function __construct(phpbb_db_driver $db, $return_statements = false) { $this->db = $db; $this->return_statements = $return_statements; -- cgit v1.2.1 From f302cbe175e99f90448458f44a499eeb33f75261 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Sun, 14 Jul 2013 10:16:15 -0400 Subject: [ticket/11696] Move file to new directory PHPBB3-11696 --- phpBB/includes/db/tools.php | 2486 ------------------------------------------- phpBB/phpbb/db/tools.php | 2486 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 2486 insertions(+), 2486 deletions(-) delete mode 100644 phpBB/includes/db/tools.php create mode 100644 phpBB/phpbb/db/tools.php (limited to 'phpBB') diff --git a/phpBB/includes/db/tools.php b/phpBB/includes/db/tools.php deleted file mode 100644 index 492284ffcd..0000000000 --- a/phpBB/includes/db/tools.php +++ /dev/null @@ -1,2486 +0,0 @@ - array( - 'INT:' => 'int(%d)', - 'BINT' => 'bigint(20)', - 'UINT' => 'mediumint(8) UNSIGNED', - 'UINT:' => 'int(%d) UNSIGNED', - 'TINT:' => 'tinyint(%d)', - 'USINT' => 'smallint(4) UNSIGNED', - 'BOOL' => 'tinyint(1) UNSIGNED', - 'VCHAR' => 'varchar(255)', - 'VCHAR:' => 'varchar(%d)', - 'CHAR:' => 'char(%d)', - 'XSTEXT' => 'text', - 'XSTEXT_UNI'=> 'varchar(100)', - 'STEXT' => 'text', - 'STEXT_UNI' => 'varchar(255)', - 'TEXT' => 'text', - 'TEXT_UNI' => 'text', - 'MTEXT' => 'mediumtext', - 'MTEXT_UNI' => 'mediumtext', - 'TIMESTAMP' => 'int(11) UNSIGNED', - 'DECIMAL' => 'decimal(5,2)', - 'DECIMAL:' => 'decimal(%d,2)', - 'PDECIMAL' => 'decimal(6,3)', - 'PDECIMAL:' => 'decimal(%d,3)', - 'VCHAR_UNI' => 'varchar(255)', - 'VCHAR_UNI:'=> 'varchar(%d)', - 'VCHAR_CI' => 'varchar(255)', - 'VARBINARY' => 'varbinary(255)', - ), - - 'mysql_40' => array( - 'INT:' => 'int(%d)', - 'BINT' => 'bigint(20)', - 'UINT' => 'mediumint(8) UNSIGNED', - 'UINT:' => 'int(%d) UNSIGNED', - 'TINT:' => 'tinyint(%d)', - 'USINT' => 'smallint(4) UNSIGNED', - 'BOOL' => 'tinyint(1) UNSIGNED', - 'VCHAR' => 'varbinary(255)', - 'VCHAR:' => 'varbinary(%d)', - 'CHAR:' => 'binary(%d)', - 'XSTEXT' => 'blob', - 'XSTEXT_UNI'=> 'blob', - 'STEXT' => 'blob', - 'STEXT_UNI' => 'blob', - 'TEXT' => 'blob', - 'TEXT_UNI' => 'blob', - 'MTEXT' => 'mediumblob', - 'MTEXT_UNI' => 'mediumblob', - 'TIMESTAMP' => 'int(11) UNSIGNED', - 'DECIMAL' => 'decimal(5,2)', - 'DECIMAL:' => 'decimal(%d,2)', - 'PDECIMAL' => 'decimal(6,3)', - 'PDECIMAL:' => 'decimal(%d,3)', - 'VCHAR_UNI' => 'blob', - 'VCHAR_UNI:'=> array('varbinary(%d)', 'limit' => array('mult', 3, 255, 'blob')), - 'VCHAR_CI' => 'blob', - 'VARBINARY' => 'varbinary(255)', - ), - - 'firebird' => array( - 'INT:' => 'INTEGER', - 'BINT' => 'DOUBLE PRECISION', - 'UINT' => 'INTEGER', - 'UINT:' => 'INTEGER', - 'TINT:' => 'INTEGER', - 'USINT' => 'INTEGER', - 'BOOL' => 'INTEGER', - 'VCHAR' => 'VARCHAR(255) CHARACTER SET NONE', - 'VCHAR:' => 'VARCHAR(%d) CHARACTER SET NONE', - 'CHAR:' => 'CHAR(%d) CHARACTER SET NONE', - 'XSTEXT' => 'BLOB SUB_TYPE TEXT CHARACTER SET NONE', - 'STEXT' => 'BLOB SUB_TYPE TEXT CHARACTER SET NONE', - 'TEXT' => 'BLOB SUB_TYPE TEXT CHARACTER SET NONE', - 'MTEXT' => 'BLOB SUB_TYPE TEXT CHARACTER SET NONE', - 'XSTEXT_UNI'=> 'VARCHAR(100) CHARACTER SET UTF8', - 'STEXT_UNI' => 'VARCHAR(255) CHARACTER SET UTF8', - 'TEXT_UNI' => 'BLOB SUB_TYPE TEXT CHARACTER SET UTF8', - 'MTEXT_UNI' => 'BLOB SUB_TYPE TEXT CHARACTER SET UTF8', - 'TIMESTAMP' => 'INTEGER', - 'DECIMAL' => 'DOUBLE PRECISION', - 'DECIMAL:' => 'DOUBLE PRECISION', - 'PDECIMAL' => 'DOUBLE PRECISION', - 'PDECIMAL:' => 'DOUBLE PRECISION', - 'VCHAR_UNI' => 'VARCHAR(255) CHARACTER SET UTF8', - 'VCHAR_UNI:'=> 'VARCHAR(%d) CHARACTER SET UTF8', - 'VCHAR_CI' => 'VARCHAR(255) CHARACTER SET UTF8', - 'VARBINARY' => 'CHAR(255) CHARACTER SET NONE', - ), - - 'mssql' => array( - 'INT:' => '[int]', - 'BINT' => '[float]', - 'UINT' => '[int]', - 'UINT:' => '[int]', - 'TINT:' => '[int]', - 'USINT' => '[int]', - 'BOOL' => '[int]', - 'VCHAR' => '[varchar] (255)', - 'VCHAR:' => '[varchar] (%d)', - 'CHAR:' => '[char] (%d)', - 'XSTEXT' => '[varchar] (1000)', - 'STEXT' => '[varchar] (3000)', - 'TEXT' => '[varchar] (8000)', - 'MTEXT' => '[text]', - 'XSTEXT_UNI'=> '[varchar] (100)', - 'STEXT_UNI' => '[varchar] (255)', - 'TEXT_UNI' => '[varchar] (4000)', - 'MTEXT_UNI' => '[text]', - 'TIMESTAMP' => '[int]', - 'DECIMAL' => '[float]', - 'DECIMAL:' => '[float]', - 'PDECIMAL' => '[float]', - 'PDECIMAL:' => '[float]', - 'VCHAR_UNI' => '[varchar] (255)', - 'VCHAR_UNI:'=> '[varchar] (%d)', - 'VCHAR_CI' => '[varchar] (255)', - 'VARBINARY' => '[varchar] (255)', - ), - - 'mssqlnative' => array( - 'INT:' => '[int]', - 'BINT' => '[float]', - 'UINT' => '[int]', - 'UINT:' => '[int]', - 'TINT:' => '[int]', - 'USINT' => '[int]', - 'BOOL' => '[int]', - 'VCHAR' => '[varchar] (255)', - 'VCHAR:' => '[varchar] (%d)', - 'CHAR:' => '[char] (%d)', - 'XSTEXT' => '[varchar] (1000)', - 'STEXT' => '[varchar] (3000)', - 'TEXT' => '[varchar] (8000)', - 'MTEXT' => '[text]', - 'XSTEXT_UNI'=> '[varchar] (100)', - 'STEXT_UNI' => '[varchar] (255)', - 'TEXT_UNI' => '[varchar] (4000)', - 'MTEXT_UNI' => '[text]', - 'TIMESTAMP' => '[int]', - 'DECIMAL' => '[float]', - 'DECIMAL:' => '[float]', - 'PDECIMAL' => '[float]', - 'PDECIMAL:' => '[float]', - 'VCHAR_UNI' => '[varchar] (255)', - 'VCHAR_UNI:'=> '[varchar] (%d)', - 'VCHAR_CI' => '[varchar] (255)', - 'VARBINARY' => '[varchar] (255)', - ), - - 'oracle' => array( - 'INT:' => 'number(%d)', - 'BINT' => 'number(20)', - 'UINT' => 'number(8)', - 'UINT:' => 'number(%d)', - 'TINT:' => 'number(%d)', - 'USINT' => 'number(4)', - 'BOOL' => 'number(1)', - 'VCHAR' => 'varchar2(255)', - 'VCHAR:' => 'varchar2(%d)', - 'CHAR:' => 'char(%d)', - 'XSTEXT' => 'varchar2(1000)', - 'STEXT' => 'varchar2(3000)', - 'TEXT' => 'clob', - 'MTEXT' => 'clob', - 'XSTEXT_UNI'=> 'varchar2(300)', - 'STEXT_UNI' => 'varchar2(765)', - 'TEXT_UNI' => 'clob', - 'MTEXT_UNI' => 'clob', - 'TIMESTAMP' => 'number(11)', - 'DECIMAL' => 'number(5, 2)', - 'DECIMAL:' => 'number(%d, 2)', - 'PDECIMAL' => 'number(6, 3)', - 'PDECIMAL:' => 'number(%d, 3)', - 'VCHAR_UNI' => 'varchar2(765)', - 'VCHAR_UNI:'=> array('varchar2(%d)', 'limit' => array('mult', 3, 765, 'clob')), - 'VCHAR_CI' => 'varchar2(255)', - 'VARBINARY' => 'raw(255)', - ), - - 'sqlite' => array( - 'INT:' => 'int(%d)', - 'BINT' => 'bigint(20)', - 'UINT' => 'INTEGER UNSIGNED', //'mediumint(8) UNSIGNED', - 'UINT:' => 'INTEGER UNSIGNED', // 'int(%d) UNSIGNED', - 'TINT:' => 'tinyint(%d)', - 'USINT' => 'INTEGER UNSIGNED', //'mediumint(4) UNSIGNED', - 'BOOL' => 'INTEGER UNSIGNED', //'tinyint(1) UNSIGNED', - 'VCHAR' => 'varchar(255)', - 'VCHAR:' => 'varchar(%d)', - 'CHAR:' => 'char(%d)', - 'XSTEXT' => 'text(65535)', - 'STEXT' => 'text(65535)', - 'TEXT' => 'text(65535)', - 'MTEXT' => 'mediumtext(16777215)', - 'XSTEXT_UNI'=> 'text(65535)', - 'STEXT_UNI' => 'text(65535)', - 'TEXT_UNI' => 'text(65535)', - 'MTEXT_UNI' => 'mediumtext(16777215)', - 'TIMESTAMP' => 'INTEGER UNSIGNED', //'int(11) UNSIGNED', - 'DECIMAL' => 'decimal(5,2)', - 'DECIMAL:' => 'decimal(%d,2)', - 'PDECIMAL' => 'decimal(6,3)', - 'PDECIMAL:' => 'decimal(%d,3)', - 'VCHAR_UNI' => 'varchar(255)', - 'VCHAR_UNI:'=> 'varchar(%d)', - 'VCHAR_CI' => 'varchar(255)', - 'VARBINARY' => 'blob', - ), - - 'postgres' => array( - 'INT:' => 'INT4', - 'BINT' => 'INT8', - 'UINT' => 'INT4', // unsigned - 'UINT:' => 'INT4', // unsigned - 'USINT' => 'INT2', // unsigned - 'BOOL' => 'INT2', // unsigned - 'TINT:' => 'INT2', - 'VCHAR' => 'varchar(255)', - 'VCHAR:' => 'varchar(%d)', - 'CHAR:' => 'char(%d)', - 'XSTEXT' => 'varchar(1000)', - 'STEXT' => 'varchar(3000)', - 'TEXT' => 'varchar(8000)', - 'MTEXT' => 'TEXT', - 'XSTEXT_UNI'=> 'varchar(100)', - 'STEXT_UNI' => 'varchar(255)', - 'TEXT_UNI' => 'varchar(4000)', - 'MTEXT_UNI' => 'TEXT', - 'TIMESTAMP' => 'INT4', // unsigned - 'DECIMAL' => 'decimal(5,2)', - 'DECIMAL:' => 'decimal(%d,2)', - 'PDECIMAL' => 'decimal(6,3)', - 'PDECIMAL:' => 'decimal(%d,3)', - 'VCHAR_UNI' => 'varchar(255)', - 'VCHAR_UNI:'=> 'varchar(%d)', - 'VCHAR_CI' => 'varchar_ci', - 'VARBINARY' => 'bytea', - ), - ); - - /** - * A list of types being unsigned for better reference in some db's - * @var array - */ - var $unsigned_types = array('UINT', 'UINT:', 'USINT', 'BOOL', 'TIMESTAMP'); - - /** - * A list of supported DBMS. We change this class to support more DBMS, the DBMS itself only need to follow some rules. - * @var array - */ - var $supported_dbms = array('firebird', 'mssql', 'mssqlnative', 'mysql_40', 'mysql_41', 'oracle', 'postgres', 'sqlite'); - - /** - * This is set to true if user only wants to return the 'to-be-executed' SQL statement(s) (as an array). - * This mode has no effect on some methods (inserting of data for example). This is expressed within the methods command. - */ - var $return_statements = false; - - /** - * Constructor. Set DB Object and set {@link $return_statements return_statements}. - * - * @param phpbb_db_driver $db Database connection - * @param bool $return_statements True if only statements should be returned and no SQL being executed - */ - public function __construct(phpbb_db_driver $db, $return_statements = false) - { - $this->db = $db; - $this->return_statements = $return_statements; - - // Determine mapping database type - switch ($this->db->sql_layer) - { - case 'mysql': - $this->sql_layer = 'mysql_40'; - break; - - case 'mysql4': - if (version_compare($this->db->sql_server_info(true), '4.1.3', '>=')) - { - $this->sql_layer = 'mysql_41'; - } - else - { - $this->sql_layer = 'mysql_40'; - } - break; - - case 'mysqli': - $this->sql_layer = 'mysql_41'; - break; - - case 'mssql': - case 'mssql_odbc': - $this->sql_layer = 'mssql'; - break; - - case 'mssqlnative': - $this->sql_layer = 'mssqlnative'; - break; - - default: - $this->sql_layer = $this->db->sql_layer; - break; - } - } - - /** - * Setter for {@link $return_statements return_statements}. - * - * @param bool $return_statements True if SQL should not be executed but returned as strings - * @return null - */ - public function set_return_statements($return_statements) - { - $this->return_statements = $return_statements; - } - - /** - * Gets a list of tables in the database. - * - * @return array Array of table names (all lower case) - */ - function sql_list_tables() - { - switch ($this->db->sql_layer) - { - case 'mysql': - case 'mysql4': - case 'mysqli': - $sql = 'SHOW TABLES'; - break; - - case 'sqlite': - $sql = 'SELECT name - FROM sqlite_master - WHERE type = "table"'; - break; - - case 'mssql': - case 'mssql_odbc': - case 'mssqlnative': - $sql = "SELECT name - FROM sysobjects - WHERE type='U'"; - break; - - case 'postgres': - $sql = 'SELECT relname - FROM pg_stat_user_tables'; - break; - - case 'firebird': - $sql = 'SELECT rdb$relation_name - FROM rdb$relations - WHERE rdb$view_source is null - AND rdb$system_flag = 0'; - break; - - case 'oracle': - $sql = 'SELECT table_name - FROM USER_TABLES'; - break; - } - - $result = $this->db->sql_query($sql); - - $tables = array(); - while ($row = $this->db->sql_fetchrow($result)) - { - $name = current($row); - $tables[$name] = $name; - } - $this->db->sql_freeresult($result); - - return $tables; - } - - /** - * Check if table exists - * - * - * @param string $table_name The table name to check for - * @return bool true if table exists, else false - */ - function sql_table_exists($table_name) - { - $this->db->sql_return_on_error(true); - $result = $this->db->sql_query_limit('SELECT * FROM ' . $table_name, 1); - $this->db->sql_return_on_error(false); - - if ($result) - { - $this->db->sql_freeresult($result); - return true; - } - - return false; - } - - /** - * Create SQL Table - * - * @param string $table_name The table name to create - * @param array $table_data Array containing table data. - * @return array Statements if $return_statements is true. - */ - function sql_create_table($table_name, $table_data) - { - // holds the DDL for a column - $columns = $statements = array(); - - if ($this->sql_table_exists($table_name)) - { - return $this->_sql_run_sql($statements); - } - - // Begin transaction - $statements[] = 'begin'; - - // Determine if we have created a PRIMARY KEY in the earliest - $primary_key_gen = false; - - // Determine if the table must be created with TEXTIMAGE - $create_textimage = false; - - // Determine if the table requires a sequence - $create_sequence = false; - - // Begin table sql statement - switch ($this->sql_layer) - { - case 'mssql': - case 'mssqlnative': - $table_sql = 'CREATE TABLE [' . $table_name . '] (' . "\n"; - break; - - default: - $table_sql = 'CREATE TABLE ' . $table_name . ' (' . "\n"; - break; - } - - // Iterate through the columns to create a table - foreach ($table_data['COLUMNS'] as $column_name => $column_data) - { - // here lies an array, filled with information compiled on the column's data - $prepared_column = $this->sql_prepare_column_data($table_name, $column_name, $column_data); - - if (isset($prepared_column['auto_increment']) && strlen($column_name) > 26) // "${column_name}_gen" - { - trigger_error("Index name '${column_name}_gen' on table '$table_name' is too long. The maximum auto increment column length is 26 characters.", E_USER_ERROR); - } - - // here we add the definition of the new column to the list of columns - switch ($this->sql_layer) - { - case 'mssql': - case 'mssqlnative': - $columns[] = "\t [{$column_name}] " . $prepared_column['column_type_sql_default']; - break; - - default: - $columns[] = "\t {$column_name} " . $prepared_column['column_type_sql']; - break; - } - - // see if we have found a primary key set due to a column definition if we have found it, we can stop looking - if (!$primary_key_gen) - { - $primary_key_gen = isset($prepared_column['primary_key_set']) && $prepared_column['primary_key_set']; - } - - // create textimage DDL based off of the existance of certain column types - if (!$create_textimage) - { - $create_textimage = isset($prepared_column['textimage']) && $prepared_column['textimage']; - } - - // create sequence DDL based off of the existance of auto incrementing columns - if (!$create_sequence && isset($prepared_column['auto_increment']) && $prepared_column['auto_increment']) - { - $create_sequence = $column_name; - } - } - - // this makes up all the columns in the create table statement - $table_sql .= implode(",\n", $columns); - - // Close the table for two DBMS and add to the statements - switch ($this->sql_layer) - { - case 'firebird': - $table_sql .= "\n);"; - $statements[] = $table_sql; - break; - - case 'mssql': - case 'mssqlnative': - $table_sql .= "\n) ON [PRIMARY]" . (($create_textimage) ? ' TEXTIMAGE_ON [PRIMARY]' : ''); - $statements[] = $table_sql; - break; - } - - // we have yet to create a primary key for this table, - // this means that we can add the one we really wanted instead - if (!$primary_key_gen) - { - // Write primary key - if (isset($table_data['PRIMARY_KEY'])) - { - if (!is_array($table_data['PRIMARY_KEY'])) - { - $table_data['PRIMARY_KEY'] = array($table_data['PRIMARY_KEY']); - } - - switch ($this->sql_layer) - { - case 'mysql_40': - case 'mysql_41': - case 'postgres': - case 'sqlite': - $table_sql .= ",\n\t PRIMARY KEY (" . implode(', ', $table_data['PRIMARY_KEY']) . ')'; - break; - - case 'firebird': - case 'mssql': - case 'mssqlnative': - // We need the data here - $old_return_statements = $this->return_statements; - $this->return_statements = true; - - $primary_key_stmts = $this->sql_create_primary_key($table_name, $table_data['PRIMARY_KEY']); - foreach ($primary_key_stmts as $pk_stmt) - { - $statements[] = $pk_stmt; - } - - $this->return_statements = $old_return_statements; - break; - - case 'oracle': - $table_sql .= ",\n\t CONSTRAINT pk_{$table_name} PRIMARY KEY (" . implode(', ', $table_data['PRIMARY_KEY']) . ')'; - break; - } - } - } - - // close the table - switch ($this->sql_layer) - { - case 'mysql_41': - // make sure the table is in UTF-8 mode - $table_sql .= "\n) CHARACTER SET `utf8` COLLATE `utf8_bin`;"; - $statements[] = $table_sql; - break; - - case 'mysql_40': - case 'sqlite': - $table_sql .= "\n);"; - $statements[] = $table_sql; - break; - - case 'postgres': - // do we need to add a sequence for auto incrementing columns? - if ($create_sequence) - { - $statements[] = "CREATE SEQUENCE {$table_name}_seq;"; - } - - $table_sql .= "\n);"; - $statements[] = $table_sql; - break; - - case 'oracle': - $table_sql .= "\n)"; - $statements[] = $table_sql; - - // do we need to add a sequence and a tigger for auto incrementing columns? - if ($create_sequence) - { - // create the actual sequence - $statements[] = "CREATE SEQUENCE {$table_name}_seq"; - - // the trigger is the mechanism by which we increment the counter - $trigger = "CREATE OR REPLACE TRIGGER t_{$table_name}\n"; - $trigger .= "BEFORE INSERT ON {$table_name}\n"; - $trigger .= "FOR EACH ROW WHEN (\n"; - $trigger .= "\tnew.{$create_sequence} IS NULL OR new.{$create_sequence} = 0\n"; - $trigger .= ")\n"; - $trigger .= "BEGIN\n"; - $trigger .= "\tSELECT {$table_name}_seq.nextval\n"; - $trigger .= "\tINTO :new.{$create_sequence}\n"; - $trigger .= "\tFROM dual;\n"; - $trigger .= "END;"; - - $statements[] = $trigger; - } - break; - - case 'firebird': - if ($create_sequence) - { - $statements[] = "CREATE GENERATOR {$table_name}_gen;"; - $statements[] = "SET GENERATOR {$table_name}_gen TO 0;"; - - $trigger = "CREATE TRIGGER t_$table_name FOR $table_name\n"; - $trigger .= "BEFORE INSERT\nAS\nBEGIN\n"; - $trigger .= "\tNEW.{$create_sequence} = GEN_ID({$table_name}_gen, 1);\nEND;"; - $statements[] = $trigger; - } - break; - } - - // Write Keys - if (isset($table_data['KEYS'])) - { - foreach ($table_data['KEYS'] as $key_name => $key_data) - { - if (!is_array($key_data[1])) - { - $key_data[1] = array($key_data[1]); - } - - $old_return_statements = $this->return_statements; - $this->return_statements = true; - - $key_stmts = ($key_data[0] == 'UNIQUE') ? $this->sql_create_unique_index($table_name, $key_name, $key_data[1]) : $this->sql_create_index($table_name, $key_name, $key_data[1]); - - foreach ($key_stmts as $key_stmt) - { - $statements[] = $key_stmt; - } - - $this->return_statements = $old_return_statements; - } - } - - // Commit Transaction - $statements[] = 'commit'; - - return $this->_sql_run_sql($statements); - } - - /** - * Handle passed database update array. - * Expected structure... - * Key being one of the following - * drop_tables: Drop tables - * add_tables: Add tables - * change_columns: Column changes (only type, not name) - * add_columns: Add columns to a table - * drop_keys: Dropping keys - * drop_columns: Removing/Dropping columns - * add_primary_keys: adding primary keys - * add_unique_index: adding an unique index - * add_index: adding an index (can be column:index_size if you need to provide size) - * - * The values are in this format: - * {TABLE NAME} => array( - * {COLUMN NAME} => array({COLUMN TYPE}, {DEFAULT VALUE}, {OPTIONAL VARIABLES}), - * {KEY/INDEX NAME} => array({COLUMN NAMES}), - * ) - * - * For more information have a look at /develop/create_schema_files.php (only available through SVN) - */ - function perform_schema_changes($schema_changes) - { - if (empty($schema_changes)) - { - return; - } - - $statements = array(); - $sqlite = false; - - // For SQLite we need to perform the schema changes in a much more different way - if ($this->db->sql_layer == 'sqlite' && $this->return_statements) - { - $sqlite_data = array(); - $sqlite = true; - } - - // Drop tables? - if (!empty($schema_changes['drop_tables'])) - { - foreach ($schema_changes['drop_tables'] as $table) - { - // only drop table if it exists - if ($this->sql_table_exists($table)) - { - $result = $this->sql_table_drop($table); - if ($this->return_statements) - { - $statements = array_merge($statements, $result); - } - } - } - } - - // Add tables? - if (!empty($schema_changes['add_tables'])) - { - foreach ($schema_changes['add_tables'] as $table => $table_data) - { - $result = $this->sql_create_table($table, $table_data); - if ($this->return_statements) - { - $statements = array_merge($statements, $result); - } - } - } - - // Change columns? - if (!empty($schema_changes['change_columns'])) - { - foreach ($schema_changes['change_columns'] as $table => $columns) - { - foreach ($columns as $column_name => $column_data) - { - // If the column exists we change it, else we add it ;) - if ($column_exists = $this->sql_column_exists($table, $column_name)) - { - $result = $this->sql_column_change($table, $column_name, $column_data, true); - } - else - { - $result = $this->sql_column_add($table, $column_name, $column_data, true); - } - - if ($sqlite) - { - if ($column_exists) - { - $sqlite_data[$table]['change_columns'][] = $result; - } - else - { - $sqlite_data[$table]['add_columns'][] = $result; - } - } - else if ($this->return_statements) - { - $statements = array_merge($statements, $result); - } - } - } - } - - // Add columns? - if (!empty($schema_changes['add_columns'])) - { - foreach ($schema_changes['add_columns'] as $table => $columns) - { - foreach ($columns as $column_name => $column_data) - { - // Only add the column if it does not exist yet - if ($column_exists = $this->sql_column_exists($table, $column_name)) - { - continue; - // This is commented out here because it can take tremendous time on updates -// $result = $this->sql_column_change($table, $column_name, $column_data, true); - } - else - { - $result = $this->sql_column_add($table, $column_name, $column_data, true); - } - - if ($sqlite) - { - if ($column_exists) - { - continue; -// $sqlite_data[$table]['change_columns'][] = $result; - } - else - { - $sqlite_data[$table]['add_columns'][] = $result; - } - } - else if ($this->return_statements) - { - $statements = array_merge($statements, $result); - } - } - } - } - - // Remove keys? - if (!empty($schema_changes['drop_keys'])) - { - foreach ($schema_changes['drop_keys'] as $table => $indexes) - { - foreach ($indexes as $index_name) - { - if (!$this->sql_index_exists($table, $index_name)) - { - continue; - } - - $result = $this->sql_index_drop($table, $index_name); - - if ($this->return_statements) - { - $statements = array_merge($statements, $result); - } - } - } - } - - // Drop columns? - if (!empty($schema_changes['drop_columns'])) - { - foreach ($schema_changes['drop_columns'] as $table => $columns) - { - foreach ($columns as $column) - { - // Only remove the column if it exists... - if ($this->sql_column_exists($table, $column)) - { - $result = $this->sql_column_remove($table, $column, true); - - if ($sqlite) - { - $sqlite_data[$table]['drop_columns'][] = $result; - } - else if ($this->return_statements) - { - $statements = array_merge($statements, $result); - } - } - } - } - } - - // Add primary keys? - if (!empty($schema_changes['add_primary_keys'])) - { - foreach ($schema_changes['add_primary_keys'] as $table => $columns) - { - $result = $this->sql_create_primary_key($table, $columns, true); - - if ($sqlite) - { - $sqlite_data[$table]['primary_key'] = $result; - } - else if ($this->return_statements) - { - $statements = array_merge($statements, $result); - } - } - } - - // Add unqiue indexes? - if (!empty($schema_changes['add_unique_index'])) - { - foreach ($schema_changes['add_unique_index'] as $table => $index_array) - { - foreach ($index_array as $index_name => $column) - { - if ($this->sql_unique_index_exists($table, $index_name)) - { - continue; - } - - $result = $this->sql_create_unique_index($table, $index_name, $column); - - if ($this->return_statements) - { - $statements = array_merge($statements, $result); - } - } - } - } - - // Add indexes? - if (!empty($schema_changes['add_index'])) - { - foreach ($schema_changes['add_index'] as $table => $index_array) - { - foreach ($index_array as $index_name => $column) - { - if ($this->sql_index_exists($table, $index_name)) - { - continue; - } - - $result = $this->sql_create_index($table, $index_name, $column); - - if ($this->return_statements) - { - $statements = array_merge($statements, $result); - } - } - } - } - - if ($sqlite) - { - foreach ($sqlite_data as $table_name => $sql_schema_changes) - { - // Create temporary table with original data - $statements[] = 'begin'; - - $sql = "SELECT sql - FROM sqlite_master - WHERE type = 'table' - AND name = '{$table_name}' - ORDER BY type DESC, name;"; - $result = $this->db->sql_query($sql); - - if (!$result) - { - continue; - } - - $row = $this->db->sql_fetchrow($result); - $this->db->sql_freeresult($result); - - // Create a backup table and populate it, destroy the existing one - $statements[] = preg_replace('#CREATE\s+TABLE\s+"?' . $table_name . '"?#i', 'CREATE TEMPORARY TABLE ' . $table_name . '_temp', $row['sql']); - $statements[] = 'INSERT INTO ' . $table_name . '_temp SELECT * FROM ' . $table_name; - $statements[] = 'DROP TABLE ' . $table_name; - - // Get the columns... - preg_match('#\((.*)\)#s', $row['sql'], $matches); - - $plain_table_cols = trim($matches[1]); - $new_table_cols = preg_split('/,(?![\s\w]+\))/m', $plain_table_cols); - $column_list = array(); - - foreach ($new_table_cols as $declaration) - { - $entities = preg_split('#\s+#', trim($declaration)); - if ($entities[0] == 'PRIMARY') - { - continue; - } - $column_list[] = $entities[0]; - } - - // note down the primary key notation because sqlite only supports adding it to the end for the new table - $primary_key = false; - $_new_cols = array(); - - foreach ($new_table_cols as $key => $declaration) - { - $entities = preg_split('#\s+#', trim($declaration)); - if ($entities[0] == 'PRIMARY') - { - $primary_key = $declaration; - continue; - } - $_new_cols[] = $declaration; - } - - $new_table_cols = $_new_cols; - - // First of all... change columns - if (!empty($sql_schema_changes['change_columns'])) - { - foreach ($sql_schema_changes['change_columns'] as $column_sql) - { - foreach ($new_table_cols as $key => $declaration) - { - $entities = preg_split('#\s+#', trim($declaration)); - if (strpos($column_sql, $entities[0] . ' ') === 0) - { - $new_table_cols[$key] = $column_sql; - } - } - } - } - - if (!empty($sql_schema_changes['add_columns'])) - { - foreach ($sql_schema_changes['add_columns'] as $column_sql) - { - $new_table_cols[] = $column_sql; - } - } - - // Now drop them... - if (!empty($sql_schema_changes['drop_columns'])) - { - foreach ($sql_schema_changes['drop_columns'] as $column_name) - { - // Remove from column list... - $new_column_list = array(); - foreach ($column_list as $key => $value) - { - if ($value === $column_name) - { - continue; - } - - $new_column_list[] = $value; - } - - $column_list = $new_column_list; - - // Remove from table... - $_new_cols = array(); - foreach ($new_table_cols as $key => $declaration) - { - $entities = preg_split('#\s+#', trim($declaration)); - if (strpos($column_name . ' ', $entities[0] . ' ') === 0) - { - continue; - } - $_new_cols[] = $declaration; - } - $new_table_cols = $_new_cols; - } - } - - // Primary key... - if (!empty($sql_schema_changes['primary_key'])) - { - $new_table_cols[] = 'PRIMARY KEY (' . implode(', ', $sql_schema_changes['primary_key']) . ')'; - } - // Add a new one or the old primary key - else if ($primary_key !== false) - { - $new_table_cols[] = $primary_key; - } - - $columns = implode(',', $column_list); - - // create a new table and fill it up. destroy the temp one - $statements[] = 'CREATE TABLE ' . $table_name . ' (' . implode(',', $new_table_cols) . ');'; - $statements[] = 'INSERT INTO ' . $table_name . ' (' . $columns . ') SELECT ' . $columns . ' FROM ' . $table_name . '_temp;'; - $statements[] = 'DROP TABLE ' . $table_name . '_temp'; - - $statements[] = 'commit'; - } - } - - if ($this->return_statements) - { - return $statements; - } - } - - /** - * Gets a list of columns of a table. - * - * @param string $table Table name - * - * @return array Array of column names (all lower case) - */ - function sql_list_columns($table) - { - $columns = array(); - - switch ($this->sql_layer) - { - case 'mysql_40': - case 'mysql_41': - $sql = "SHOW COLUMNS FROM $table"; - break; - - // PostgreSQL has a way of doing this in a much simpler way but would - // not allow us to support all versions of PostgreSQL - case 'postgres': - $sql = "SELECT a.attname - FROM pg_class c, pg_attribute a - WHERE c.relname = '{$table}' - AND a.attnum > 0 - AND a.attrelid = c.oid"; - break; - - // same deal with PostgreSQL, we must perform more complex operations than - // we technically could - case 'mssql': - case 'mssqlnative': - $sql = "SELECT c.name - FROM syscolumns c - LEFT JOIN sysobjects o ON c.id = o.id - WHERE o.name = '{$table}'"; - break; - - case 'oracle': - $sql = "SELECT column_name - FROM user_tab_columns - WHERE LOWER(table_name) = '" . strtolower($table) . "'"; - break; - - case 'firebird': - $sql = "SELECT RDB\$FIELD_NAME as FNAME - FROM RDB\$RELATION_FIELDS - WHERE RDB\$RELATION_NAME = '" . strtoupper($table) . "'"; - break; - - case 'sqlite': - $sql = "SELECT sql - FROM sqlite_master - WHERE type = 'table' - AND name = '{$table}'"; - - $result = $this->db->sql_query($sql); - - if (!$result) - { - return false; - } - - $row = $this->db->sql_fetchrow($result); - $this->db->sql_freeresult($result); - - preg_match('#\((.*)\)#s', $row['sql'], $matches); - - $cols = trim($matches[1]); - $col_array = preg_split('/,(?![\s\w]+\))/m', $cols); - - foreach ($col_array as $declaration) - { - $entities = preg_split('#\s+#', trim($declaration)); - if ($entities[0] == 'PRIMARY') - { - continue; - } - - $column = strtolower($entities[0]); - $columns[$column] = $column; - } - - return $columns; - break; - } - - $result = $this->db->sql_query($sql); - - while ($row = $this->db->sql_fetchrow($result)) - { - $column = strtolower(current($row)); - $columns[$column] = $column; - } - $this->db->sql_freeresult($result); - - return $columns; - } - - /** - * Check whether a specified column exist in a table - * - * @param string $table Table to check - * @param string $column_name Column to check - * - * @return bool True if column exists, false otherwise - */ - function sql_column_exists($table, $column_name) - { - $columns = $this->sql_list_columns($table); - - return isset($columns[$column_name]); - } - - /** - * Check if a specified index exists in table. Does not return PRIMARY KEY and UNIQUE indexes. - * - * @param string $table_name Table to check the index at - * @param string $index_name The index name to check - * - * @return bool True if index exists, else false - */ - function sql_index_exists($table_name, $index_name) - { - if ($this->sql_layer == 'mssql' || $this->sql_layer == 'mssqlnative') - { - $sql = "EXEC sp_statistics '$table_name'"; - $result = $this->db->sql_query($sql); - - while ($row = $this->db->sql_fetchrow($result)) - { - if ($row['TYPE'] == 3) - { - if (strtolower($row['INDEX_NAME']) == strtolower($index_name)) - { - $this->db->sql_freeresult($result); - return true; - } - } - } - $this->db->sql_freeresult($result); - - return false; - } - - switch ($this->sql_layer) - { - case 'firebird': - $sql = "SELECT LOWER(RDB\$INDEX_NAME) as index_name - FROM RDB\$INDICES - WHERE RDB\$RELATION_NAME = '" . strtoupper($table_name) . "' - AND RDB\$UNIQUE_FLAG IS NULL - AND RDB\$FOREIGN_KEY IS NULL"; - $col = 'index_name'; - break; - - case 'postgres': - $sql = "SELECT ic.relname as index_name - FROM pg_class bc, pg_class ic, pg_index i - WHERE (bc.oid = i.indrelid) - AND (ic.oid = i.indexrelid) - AND (bc.relname = '" . $table_name . "') - AND (i.indisunique != 't') - AND (i.indisprimary != 't')"; - $col = 'index_name'; - break; - - case 'mysql_40': - case 'mysql_41': - $sql = 'SHOW KEYS - FROM ' . $table_name; - $col = 'Key_name'; - break; - - case 'oracle': - $sql = "SELECT index_name - FROM user_indexes - WHERE table_name = '" . strtoupper($table_name) . "' - AND generated = 'N' - AND uniqueness = 'NONUNIQUE'"; - $col = 'index_name'; - break; - - case 'sqlite': - $sql = "PRAGMA index_list('" . $table_name . "');"; - $col = 'name'; - break; - } - - $result = $this->db->sql_query($sql); - while ($row = $this->db->sql_fetchrow($result)) - { - if (($this->sql_layer == 'mysql_40' || $this->sql_layer == 'mysql_41') && !$row['Non_unique']) - { - continue; - } - - // These DBMS prefix index name with the table name - switch ($this->sql_layer) - { - case 'firebird': - case 'oracle': - case 'postgres': - case 'sqlite': - $row[$col] = substr($row[$col], strlen($table_name) + 1); - break; - } - - if (strtolower($row[$col]) == strtolower($index_name)) - { - $this->db->sql_freeresult($result); - return true; - } - } - $this->db->sql_freeresult($result); - - return false; - } - - /** - * Check if a specified index exists in table. Does not return PRIMARY KEY and UNIQUE indexes. - * - * @param string $table_name Table to check the index at - * @param string $index_name The index name to check - * - * @return bool True if index exists, else false - */ - function sql_unique_index_exists($table_name, $index_name) - { - if ($this->sql_layer == 'mssql' || $this->sql_layer == 'mssqlnative') - { - $sql = "EXEC sp_statistics '$table_name'"; - $result = $this->db->sql_query($sql); - - while ($row = $this->db->sql_fetchrow($result)) - { - // Usually NON_UNIQUE is the column we want to check, but we allow for both - if ($row['TYPE'] == 3) - { - if (strtolower($row['INDEX_NAME']) == strtolower($index_name)) - { - $this->db->sql_freeresult($result); - return true; - } - } - } - $this->db->sql_freeresult($result); - return false; - } - - switch ($this->sql_layer) - { - case 'firebird': - $sql = "SELECT LOWER(RDB\$INDEX_NAME) as index_name - FROM RDB\$INDICES - WHERE RDB\$RELATION_NAME = '" . strtoupper($table_name) . "' - AND RDB\$UNIQUE_FLAG IS NOT NULL - AND RDB\$FOREIGN_KEY IS NULL"; - $col = 'index_name'; - break; - - case 'postgres': - $sql = "SELECT ic.relname as index_name, i.indisunique - FROM pg_class bc, pg_class ic, pg_index i - WHERE (bc.oid = i.indrelid) - AND (ic.oid = i.indexrelid) - AND (bc.relname = '" . $table_name . "') - AND (i.indisprimary != 't')"; - $col = 'index_name'; - break; - - case 'mysql_40': - case 'mysql_41': - $sql = 'SHOW KEYS - FROM ' . $table_name; - $col = 'Key_name'; - break; - - case 'oracle': - $sql = "SELECT index_name, table_owner - FROM user_indexes - WHERE table_name = '" . strtoupper($table_name) . "' - AND generated = 'N' - AND uniqueness = 'UNIQUE'"; - $col = 'index_name'; - break; - - case 'sqlite': - $sql = "PRAGMA index_list('" . $table_name . "');"; - $col = 'name'; - break; - } - - $result = $this->db->sql_query($sql); - while ($row = $this->db->sql_fetchrow($result)) - { - if (($this->sql_layer == 'mysql_40' || $this->sql_layer == 'mysql_41') && ($row['Non_unique'] || $row[$col] == 'PRIMARY')) - { - continue; - } - - if ($this->sql_layer == 'sqlite' && !$row['unique']) - { - continue; - } - - if ($this->sql_layer == 'postgres' && $row['indisunique'] != 't') - { - continue; - } - - // These DBMS prefix index name with the table name - switch ($this->sql_layer) - { - case 'oracle': - // Two cases here... prefixed with U_[table_owner] and not prefixed with table_name - if (strpos($row[$col], 'U_') === 0) - { - $row[$col] = substr($row[$col], strlen('U_' . $row['table_owner']) + 1); - } - else if (strpos($row[$col], strtoupper($table_name)) === 0) - { - $row[$col] = substr($row[$col], strlen($table_name) + 1); - } - break; - - case 'firebird': - case 'postgres': - case 'sqlite': - $row[$col] = substr($row[$col], strlen($table_name) + 1); - break; - } - - if (strtolower($row[$col]) == strtolower($index_name)) - { - $this->db->sql_freeresult($result); - return true; - } - } - $this->db->sql_freeresult($result); - - return false; - } - - /** - * Private method for performing sql statements (either execute them or return them) - * @access private - */ - function _sql_run_sql($statements) - { - if ($this->return_statements) - { - return $statements; - } - - // We could add error handling here... - foreach ($statements as $sql) - { - if ($sql === 'begin') - { - $this->db->sql_transaction('begin'); - } - else if ($sql === 'commit') - { - $this->db->sql_transaction('commit'); - } - else - { - $this->db->sql_query($sql); - } - } - - return true; - } - - /** - * Function to prepare some column information for better usage - * @access private - */ - function sql_prepare_column_data($table_name, $column_name, $column_data) - { - if (strlen($column_name) > 30) - { - trigger_error("Column name '$column_name' on table '$table_name' is too long. The maximum is 30 characters.", E_USER_ERROR); - } - - // Get type - if (strpos($column_data[0], ':') !== false) - { - list($orig_column_type, $column_length) = explode(':', $column_data[0]); - if (!is_array($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':'])) - { - $column_type = sprintf($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':'], $column_length); - } - else - { - if (isset($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['rule'])) - { - switch ($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['rule'][0]) - { - case 'div': - $column_length /= $this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['rule'][1]; - $column_length = ceil($column_length); - $column_type = sprintf($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':'][0], $column_length); - break; - } - } - - if (isset($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'])) - { - switch ($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'][0]) - { - case 'mult': - $column_length *= $this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'][1]; - if ($column_length > $this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'][2]) - { - $column_type = $this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'][3]; - } - else - { - $column_type = sprintf($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':'][0], $column_length); - } - break; - } - } - } - $orig_column_type .= ':'; - } - else - { - $orig_column_type = $column_data[0]; - $column_type = $this->dbms_type_map[$this->sql_layer][$column_data[0]]; - } - - // Adjust default value if db-dependent specified - if (is_array($column_data[1])) - { - $column_data[1] = (isset($column_data[1][$this->sql_layer])) ? $column_data[1][$this->sql_layer] : $column_data[1]['default']; - } - - $sql = ''; - - $return_array = array(); - - switch ($this->sql_layer) - { - case 'firebird': - $sql .= " {$column_type} "; - $return_array['column_type_sql_type'] = " {$column_type} "; - - if (!is_null($column_data[1])) - { - $sql .= 'DEFAULT ' . ((is_numeric($column_data[1])) ? $column_data[1] : "'{$column_data[1]}'") . ' '; - $return_array['column_type_sql_default'] = ((is_numeric($column_data[1])) ? $column_data[1] : "'{$column_data[1]}'") . ' '; - } - - $sql .= 'NOT NULL'; - - // This is a UNICODE column and thus should be given it's fair share - if (preg_match('/^X?STEXT_UNI|VCHAR_(CI|UNI:?)/', $column_data[0])) - { - $sql .= ' COLLATE UNICODE'; - } - - $return_array['auto_increment'] = false; - if (isset($column_data[2]) && $column_data[2] == 'auto_increment') - { - $return_array['auto_increment'] = true; - } - - break; - - case 'mssql': - case 'mssqlnative': - $sql .= " {$column_type} "; - $sql_default = " {$column_type} "; - - // For adding columns we need the default definition - if (!is_null($column_data[1])) - { - // For hexadecimal values do not use single quotes - if (strpos($column_data[1], '0x') === 0) - { - $return_array['default'] = 'DEFAULT (' . $column_data[1] . ') '; - $sql_default .= $return_array['default']; - } - else - { - $return_array['default'] = 'DEFAULT (' . ((is_numeric($column_data[1])) ? $column_data[1] : "'{$column_data[1]}'") . ') '; - $sql_default .= $return_array['default']; - } - } - - if (isset($column_data[2]) && $column_data[2] == 'auto_increment') - { -// $sql .= 'IDENTITY (1, 1) '; - $sql_default .= 'IDENTITY (1, 1) '; - } - - $return_array['textimage'] = $column_type === '[text]'; - - $sql .= 'NOT NULL'; - $sql_default .= 'NOT NULL'; - - $return_array['column_type_sql_default'] = $sql_default; - - break; - - case 'mysql_40': - case 'mysql_41': - $sql .= " {$column_type} "; - - // For hexadecimal values do not use single quotes - if (!is_null($column_data[1]) && substr($column_type, -4) !== 'text' && substr($column_type, -4) !== 'blob') - { - $sql .= (strpos($column_data[1], '0x') === 0) ? "DEFAULT {$column_data[1]} " : "DEFAULT '{$column_data[1]}' "; - } - $sql .= 'NOT NULL'; - - if (isset($column_data[2])) - { - if ($column_data[2] == 'auto_increment') - { - $sql .= ' auto_increment'; - } - else if ($this->sql_layer === 'mysql_41' && $column_data[2] == 'true_sort') - { - $sql .= ' COLLATE utf8_unicode_ci'; - } - } - - break; - - case 'oracle': - $sql .= " {$column_type} "; - $sql .= (!is_null($column_data[1])) ? "DEFAULT '{$column_data[1]}' " : ''; - - // In Oracle empty strings ('') are treated as NULL. - // Therefore in oracle we allow NULL's for all DEFAULT '' entries - // Oracle does not like setting NOT NULL on a column that is already NOT NULL (this happens only on number fields) - if (!preg_match('/number/i', $column_type)) - { - $sql .= ($column_data[1] === '') ? '' : 'NOT NULL'; - } - - $return_array['auto_increment'] = false; - if (isset($column_data[2]) && $column_data[2] == 'auto_increment') - { - $return_array['auto_increment'] = true; - } - - break; - - case 'postgres': - $return_array['column_type'] = $column_type; - - $sql .= " {$column_type} "; - - $return_array['auto_increment'] = false; - if (isset($column_data[2]) && $column_data[2] == 'auto_increment') - { - $default_val = "nextval('{$table_name}_seq')"; - $return_array['auto_increment'] = true; - } - else if (!is_null($column_data[1])) - { - $default_val = "'" . $column_data[1] . "'"; - $return_array['null'] = 'NOT NULL'; - $sql .= 'NOT NULL '; - } - - $return_array['default'] = $default_val; - - $sql .= "DEFAULT {$default_val}"; - - // Unsigned? Then add a CHECK contraint - if (in_array($orig_column_type, $this->unsigned_types)) - { - $return_array['constraint'] = "CHECK ({$column_name} >= 0)"; - $sql .= " CHECK ({$column_name} >= 0)"; - } - - break; - - case 'sqlite': - $return_array['primary_key_set'] = false; - if (isset($column_data[2]) && $column_data[2] == 'auto_increment') - { - $sql .= ' INTEGER PRIMARY KEY'; - $return_array['primary_key_set'] = true; - } - else - { - $sql .= ' ' . $column_type; - } - - $sql .= ' NOT NULL '; - $sql .= (!is_null($column_data[1])) ? "DEFAULT '{$column_data[1]}'" : ''; - - break; - } - - $return_array['column_type_sql'] = $sql; - - return $return_array; - } - - /** - * Add new column - */ - function sql_column_add($table_name, $column_name, $column_data, $inline = false) - { - $column_data = $this->sql_prepare_column_data($table_name, $column_name, $column_data); - $statements = array(); - - switch ($this->sql_layer) - { - case 'firebird': - // Does not support AFTER statement, only POSITION (and there you need the column position) - $statements[] = 'ALTER TABLE ' . $table_name . ' ADD "' . strtoupper($column_name) . '" ' . $column_data['column_type_sql']; - break; - - case 'mssql': - case 'mssqlnative': - // Does not support AFTER, only through temporary table - $statements[] = 'ALTER TABLE [' . $table_name . '] ADD [' . $column_name . '] ' . $column_data['column_type_sql_default']; - break; - - case 'mysql_40': - case 'mysql_41': - $after = (!empty($column_data['after'])) ? ' AFTER ' . $column_data['after'] : ''; - $statements[] = 'ALTER TABLE `' . $table_name . '` ADD COLUMN `' . $column_name . '` ' . $column_data['column_type_sql'] . $after; - break; - - case 'oracle': - // Does not support AFTER, only through temporary table - $statements[] = 'ALTER TABLE ' . $table_name . ' ADD ' . $column_name . ' ' . $column_data['column_type_sql']; - break; - - case 'postgres': - // Does not support AFTER, only through temporary table - if (version_compare($this->db->sql_server_info(true), '8.0', '>=')) - { - $statements[] = 'ALTER TABLE ' . $table_name . ' ADD COLUMN "' . $column_name . '" ' . $column_data['column_type_sql']; - } - else - { - // old versions cannot add columns with default and null information - $statements[] = 'ALTER TABLE ' . $table_name . ' ADD COLUMN "' . $column_name . '" ' . $column_data['column_type'] . ' ' . $column_data['constraint']; - - if (isset($column_data['null'])) - { - if ($column_data['null'] == 'NOT NULL') - { - $statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN ' . $column_name . ' SET NOT NULL'; - } - } - - if (isset($column_data['default'])) - { - $statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN ' . $column_name . ' SET DEFAULT ' . $column_data['default']; - } - } - - break; - - case 'sqlite': - - if ($inline && $this->return_statements) - { - return $column_name . ' ' . $column_data['column_type_sql']; - } - - if (version_compare(sqlite_libversion(), '3.0') == -1) - { - $sql = "SELECT sql - FROM sqlite_master - WHERE type = 'table' - AND name = '{$table_name}' - ORDER BY type DESC, name;"; - $result = $this->db->sql_query($sql); - - if (!$result) - { - break; - } - - $row = $this->db->sql_fetchrow($result); - $this->db->sql_freeresult($result); - - $statements[] = 'begin'; - - // Create a backup table and populate it, destroy the existing one - $statements[] = preg_replace('#CREATE\s+TABLE\s+"?' . $table_name . '"?#i', 'CREATE TEMPORARY TABLE ' . $table_name . '_temp', $row['sql']); - $statements[] = 'INSERT INTO ' . $table_name . '_temp SELECT * FROM ' . $table_name; - $statements[] = 'DROP TABLE ' . $table_name; - - preg_match('#\((.*)\)#s', $row['sql'], $matches); - - $new_table_cols = trim($matches[1]); - $old_table_cols = preg_split('/,(?![\s\w]+\))/m', $new_table_cols); - $column_list = array(); - - foreach ($old_table_cols as $declaration) - { - $entities = preg_split('#\s+#', trim($declaration)); - if ($entities[0] == 'PRIMARY') - { - continue; - } - $column_list[] = $entities[0]; - } - - $columns = implode(',', $column_list); - - $new_table_cols = $column_name . ' ' . $column_data['column_type_sql'] . ',' . $new_table_cols; - - // create a new table and fill it up. destroy the temp one - $statements[] = 'CREATE TABLE ' . $table_name . ' (' . $new_table_cols . ');'; - $statements[] = 'INSERT INTO ' . $table_name . ' (' . $columns . ') SELECT ' . $columns . ' FROM ' . $table_name . '_temp;'; - $statements[] = 'DROP TABLE ' . $table_name . '_temp'; - - $statements[] = 'commit'; - } - else - { - $statements[] = 'ALTER TABLE ' . $table_name . ' ADD ' . $column_name . ' [' . $column_data['column_type_sql'] . ']'; - } - break; - } - - return $this->_sql_run_sql($statements); - } - - /** - * Drop column - */ - function sql_column_remove($table_name, $column_name, $inline = false) - { - $statements = array(); - - switch ($this->sql_layer) - { - case 'firebird': - $statements[] = 'ALTER TABLE ' . $table_name . ' DROP "' . strtoupper($column_name) . '"'; - break; - - case 'mssql': - case 'mssqlnative': - // remove default cosntraints first - // http://msdn.microsoft.com/en-us/library/aa175912%28v=sql.80%29.aspx - $statements[] = "DECLARE @drop_default_name VARCHAR(100), @cmd VARCHAR(1000) - SET @drop_default_name = - (SELECT so.name FROM sysobjects so - JOIN sysconstraints sc ON so.id = sc.constid - WHERE object_name(so.parent_obj) = '{$table_name}' - AND so.xtype = 'D' - AND sc.colid = (SELECT colid FROM syscolumns - WHERE id = object_id('{$table_name}') - AND name = '{$column_name}')) - IF @drop_default_name <> '' - BEGIN - SET @cmd = 'ALTER TABLE [{$table_name}] DROP CONSTRAINT [' + @drop_default_name + ']' - EXEC(@cmd) - END"; - $statements[] = 'ALTER TABLE [' . $table_name . '] DROP COLUMN [' . $column_name . ']'; - break; - - case 'mysql_40': - case 'mysql_41': - $statements[] = 'ALTER TABLE `' . $table_name . '` DROP COLUMN `' . $column_name . '`'; - break; - - case 'oracle': - $statements[] = 'ALTER TABLE ' . $table_name . ' DROP COLUMN ' . $column_name; - break; - - case 'postgres': - $statements[] = 'ALTER TABLE ' . $table_name . ' DROP COLUMN "' . $column_name . '"'; - break; - - case 'sqlite': - - if ($inline && $this->return_statements) - { - return $column_name; - } - - if (version_compare(sqlite_libversion(), '3.0') == -1) - { - $sql = "SELECT sql - FROM sqlite_master - WHERE type = 'table' - AND name = '{$table_name}' - ORDER BY type DESC, name;"; - $result = $this->db->sql_query($sql); - - if (!$result) - { - break; - } - - $row = $this->db->sql_fetchrow($result); - $this->db->sql_freeresult($result); - - $statements[] = 'begin'; - - // Create a backup table and populate it, destroy the existing one - $statements[] = preg_replace('#CREATE\s+TABLE\s+"?' . $table_name . '"?#i', 'CREATE TEMPORARY TABLE ' . $table_name . '_temp', $row['sql']); - $statements[] = 'INSERT INTO ' . $table_name . '_temp SELECT * FROM ' . $table_name; - $statements[] = 'DROP TABLE ' . $table_name; - - preg_match('#\((.*)\)#s', $row['sql'], $matches); - - $new_table_cols = trim($matches[1]); - $old_table_cols = preg_split('/,(?![\s\w]+\))/m', $new_table_cols); - $column_list = array(); - - foreach ($old_table_cols as $declaration) - { - $entities = preg_split('#\s+#', trim($declaration)); - if ($entities[0] == 'PRIMARY' || $entities[0] === $column_name) - { - continue; - } - $column_list[] = $entities[0]; - } - - $columns = implode(',', $column_list); - - $new_table_cols = preg_replace('/' . $column_name . '[^,]+(?:,|$)/m', '', $new_table_cols); - - // create a new table and fill it up. destroy the temp one - $statements[] = 'CREATE TABLE ' . $table_name . ' (' . $new_table_cols . ');'; - $statements[] = 'INSERT INTO ' . $table_name . ' (' . $columns . ') SELECT ' . $columns . ' FROM ' . $table_name . '_temp;'; - $statements[] = 'DROP TABLE ' . $table_name . '_temp'; - - $statements[] = 'commit'; - } - else - { - $statements[] = 'ALTER TABLE ' . $table_name . ' DROP COLUMN ' . $column_name; - } - break; - } - - return $this->_sql_run_sql($statements); - } - - /** - * Drop Index - */ - function sql_index_drop($table_name, $index_name) - { - $statements = array(); - - switch ($this->sql_layer) - { - case 'mssql': - case 'mssqlnative': - $statements[] = 'DROP INDEX ' . $table_name . '.' . $index_name; - break; - - case 'mysql_40': - case 'mysql_41': - $statements[] = 'DROP INDEX ' . $index_name . ' ON ' . $table_name; - break; - - case 'firebird': - case 'oracle': - case 'postgres': - case 'sqlite': - $statements[] = 'DROP INDEX ' . $table_name . '_' . $index_name; - break; - } - - return $this->_sql_run_sql($statements); - } - - /** - * Drop Table - */ - function sql_table_drop($table_name) - { - $statements = array(); - - if (!$this->sql_table_exists($table_name)) - { - return $this->_sql_run_sql($statements); - } - - // the most basic operation, get rid of the table - $statements[] = 'DROP TABLE ' . $table_name; - - switch ($this->sql_layer) - { - case 'firebird': - $sql = 'SELECT RDB$GENERATOR_NAME as gen - FROM RDB$GENERATORS - WHERE RDB$SYSTEM_FLAG = 0 - AND RDB$GENERATOR_NAME = \'' . strtoupper($table_name) . "_GEN'"; - $result = $this->db->sql_query($sql); - - // does a generator exist? - if ($row = $this->db->sql_fetchrow($result)) - { - $statements[] = "DROP GENERATOR {$row['gen']};"; - } - $this->db->sql_freeresult($result); - break; - - case 'oracle': - $sql = 'SELECT A.REFERENCED_NAME - FROM USER_DEPENDENCIES A, USER_TRIGGERS B - WHERE A.REFERENCED_TYPE = \'SEQUENCE\' - AND A.NAME = B.TRIGGER_NAME - AND B.TABLE_NAME = \'' . strtoupper($table_name) . "'"; - $result = $this->db->sql_query($sql); - - // any sequences ref'd to this table's triggers? - while ($row = $this->db->sql_fetchrow($result)) - { - $statements[] = "DROP SEQUENCE {$row['referenced_name']}"; - } - $this->db->sql_freeresult($result); - break; - - case 'postgres': - // PGSQL does not "tightly" bind sequences and tables, we must guess... - $sql = "SELECT relname - FROM pg_class - WHERE relkind = 'S' - AND relname = '{$table_name}_seq'"; - $result = $this->db->sql_query($sql); - - // We don't even care about storing the results. We already know the answer if we get rows back. - if ($this->db->sql_fetchrow($result)) - { - $statements[] = "DROP SEQUENCE {$table_name}_seq;\n"; - } - $this->db->sql_freeresult($result); - break; - } - - return $this->_sql_run_sql($statements); - } - - /** - * Add primary key - */ - function sql_create_primary_key($table_name, $column, $inline = false) - { - $statements = array(); - - switch ($this->sql_layer) - { - case 'firebird': - case 'postgres': - case 'mysql_40': - case 'mysql_41': - $statements[] = 'ALTER TABLE ' . $table_name . ' ADD PRIMARY KEY (' . implode(', ', $column) . ')'; - break; - - case 'mssql': - case 'mssqlnative': - $sql = "ALTER TABLE [{$table_name}] WITH NOCHECK ADD "; - $sql .= "CONSTRAINT [PK_{$table_name}] PRIMARY KEY CLUSTERED ("; - $sql .= '[' . implode("],\n\t\t[", $column) . ']'; - $sql .= ') ON [PRIMARY]'; - - $statements[] = $sql; - break; - - case 'oracle': - $statements[] = 'ALTER TABLE ' . $table_name . 'add CONSTRAINT pk_' . $table_name . ' PRIMARY KEY (' . implode(', ', $column) . ')'; - break; - - case 'sqlite': - - if ($inline && $this->return_statements) - { - return $column; - } - - $sql = "SELECT sql - FROM sqlite_master - WHERE type = 'table' - AND name = '{$table_name}' - ORDER BY type DESC, name;"; - $result = $this->db->sql_query($sql); - - if (!$result) - { - break; - } - - $row = $this->db->sql_fetchrow($result); - $this->db->sql_freeresult($result); - - $statements[] = 'begin'; - - // Create a backup table and populate it, destroy the existing one - $statements[] = preg_replace('#CREATE\s+TABLE\s+"?' . $table_name . '"?#i', 'CREATE TEMPORARY TABLE ' . $table_name . '_temp', $row['sql']); - $statements[] = 'INSERT INTO ' . $table_name . '_temp SELECT * FROM ' . $table_name; - $statements[] = 'DROP TABLE ' . $table_name; - - preg_match('#\((.*)\)#s', $row['sql'], $matches); - - $new_table_cols = trim($matches[1]); - $old_table_cols = preg_split('/,(?![\s\w]+\))/m', $new_table_cols); - $column_list = array(); - - foreach ($old_table_cols as $declaration) - { - $entities = preg_split('#\s+#', trim($declaration)); - if ($entities[0] == 'PRIMARY') - { - continue; - } - $column_list[] = $entities[0]; - } - - $columns = implode(',', $column_list); - - // create a new table and fill it up. destroy the temp one - $statements[] = 'CREATE TABLE ' . $table_name . ' (' . $new_table_cols . ', PRIMARY KEY (' . implode(', ', $column) . '));'; - $statements[] = 'INSERT INTO ' . $table_name . ' (' . $columns . ') SELECT ' . $columns . ' FROM ' . $table_name . '_temp;'; - $statements[] = 'DROP TABLE ' . $table_name . '_temp'; - - $statements[] = 'commit'; - break; - } - - return $this->_sql_run_sql($statements); - } - - /** - * Add unique index - */ - function sql_create_unique_index($table_name, $index_name, $column) - { - $statements = array(); - - $table_prefix = substr(CONFIG_TABLE, 0, -6); // strlen(config) - if (strlen($table_name . $index_name) - strlen($table_prefix) > 24) - { - $max_length = strlen($table_prefix) + 24; - trigger_error("Index name '{$table_name}_$index_name' on table '$table_name' is too long. The maximum is $max_length characters.", E_USER_ERROR); - } - - switch ($this->sql_layer) - { - case 'firebird': - case 'postgres': - case 'oracle': - case 'sqlite': - $statements[] = 'CREATE UNIQUE INDEX ' . $table_name . '_' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ')'; - break; - - case 'mysql_40': - case 'mysql_41': - $statements[] = 'ALTER TABLE ' . $table_name . ' ADD UNIQUE INDEX ' . $index_name . '(' . implode(', ', $column) . ')'; - break; - - case 'mssql': - case 'mssqlnative': - $statements[] = 'CREATE UNIQUE INDEX ' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ') ON [PRIMARY]'; - break; - } - - return $this->_sql_run_sql($statements); - } - - /** - * Add index - */ - function sql_create_index($table_name, $index_name, $column) - { - $statements = array(); - - $table_prefix = substr(CONFIG_TABLE, 0, -6); // strlen(config) - if (strlen($table_name . $index_name) - strlen($table_prefix) > 24) - { - $max_length = strlen($table_prefix) + 24; - trigger_error("Index name '{$table_name}_$index_name' on table '$table_name' is too long. The maximum is $max_length characters.", E_USER_ERROR); - } - - // remove index length unless MySQL4 - if ('mysql_40' != $this->sql_layer) - { - $column = preg_replace('#:.*$#', '', $column); - } - - switch ($this->sql_layer) - { - case 'firebird': - case 'postgres': - case 'oracle': - case 'sqlite': - $statements[] = 'CREATE INDEX ' . $table_name . '_' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ')'; - break; - - case 'mysql_40': - // add index size to definition as required by MySQL4 - foreach ($column as $i => $col) - { - if (false !== strpos($col, ':')) - { - list($col, $index_size) = explode(':', $col); - $column[$i] = "$col($index_size)"; - } - } - // no break - case 'mysql_41': - $statements[] = 'ALTER TABLE ' . $table_name . ' ADD INDEX ' . $index_name . '(' . implode(', ', $column) . ')'; - break; - - case 'mssql': - case 'mssqlnative': - $statements[] = 'CREATE INDEX ' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ') ON [PRIMARY]'; - break; - } - - return $this->_sql_run_sql($statements); - } - - /** - * List all of the indices that belong to a table, - * does not count: - * * UNIQUE indices - * * PRIMARY keys - */ - function sql_list_index($table_name) - { - $index_array = array(); - - if ($this->sql_layer == 'mssql' || $this->sql_layer == 'mssqlnative') - { - $sql = "EXEC sp_statistics '$table_name'"; - $result = $this->db->sql_query($sql); - while ($row = $this->db->sql_fetchrow($result)) - { - if ($row['TYPE'] == 3) - { - $index_array[] = $row['INDEX_NAME']; - } - } - $this->db->sql_freeresult($result); - } - else - { - switch ($this->sql_layer) - { - case 'firebird': - $sql = "SELECT LOWER(RDB\$INDEX_NAME) as index_name - FROM RDB\$INDICES - WHERE RDB\$RELATION_NAME = '" . strtoupper($table_name) . "' - AND RDB\$UNIQUE_FLAG IS NULL - AND RDB\$FOREIGN_KEY IS NULL"; - $col = 'index_name'; - break; - - case 'postgres': - $sql = "SELECT ic.relname as index_name - FROM pg_class bc, pg_class ic, pg_index i - WHERE (bc.oid = i.indrelid) - AND (ic.oid = i.indexrelid) - AND (bc.relname = '" . $table_name . "') - AND (i.indisunique != 't') - AND (i.indisprimary != 't')"; - $col = 'index_name'; - break; - - case 'mysql_40': - case 'mysql_41': - $sql = 'SHOW KEYS - FROM ' . $table_name; - $col = 'Key_name'; - break; - - case 'oracle': - $sql = "SELECT index_name - FROM user_indexes - WHERE table_name = '" . strtoupper($table_name) . "' - AND generated = 'N' - AND uniqueness = 'NONUNIQUE'"; - $col = 'index_name'; - break; - - case 'sqlite': - $sql = "PRAGMA index_info('" . $table_name . "');"; - $col = 'name'; - break; - } - - $result = $this->db->sql_query($sql); - while ($row = $this->db->sql_fetchrow($result)) - { - if (($this->sql_layer == 'mysql_40' || $this->sql_layer == 'mysql_41') && !$row['Non_unique']) - { - continue; - } - - switch ($this->sql_layer) - { - case 'firebird': - case 'oracle': - case 'postgres': - case 'sqlite': - $row[$col] = substr($row[$col], strlen($table_name) + 1); - break; - } - - $index_array[] = $row[$col]; - } - $this->db->sql_freeresult($result); - } - - return array_map('strtolower', $index_array); - } - - /** - * Change column type (not name!) - */ - function sql_column_change($table_name, $column_name, $column_data, $inline = false) - { - $column_data = $this->sql_prepare_column_data($table_name, $column_name, $column_data); - $statements = array(); - - switch ($this->sql_layer) - { - case 'firebird': - // Change type... - if (!empty($column_data['column_type_sql_default'])) - { - $statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN "' . strtoupper($column_name) . '" TYPE ' . ' ' . $column_data['column_type_sql_type']; - $statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN "' . strtoupper($column_name) . '" SET DEFAULT ' . ' ' . $column_data['column_type_sql_default']; - } - else - { - // TODO: try to change pkey without removing trigger, generator or constraints. ATM this query may fail. - $statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN "' . strtoupper($column_name) . '" TYPE ' . ' ' . $column_data['column_type_sql_type']; - } - break; - - case 'mssql': - case 'mssqlnative': - $statements[] = 'ALTER TABLE [' . $table_name . '] ALTER COLUMN [' . $column_name . '] ' . $column_data['column_type_sql']; - - if (!empty($column_data['default'])) - { - // Using TRANSACT-SQL for this statement because we do not want to have colliding data if statements are executed at a later stage - $statements[] = "DECLARE @drop_default_name VARCHAR(100), @cmd VARCHAR(1000) - SET @drop_default_name = - (SELECT so.name FROM sysobjects so - JOIN sysconstraints sc ON so.id = sc.constid - WHERE object_name(so.parent_obj) = '{$table_name}' - AND so.xtype = 'D' - AND sc.colid = (SELECT colid FROM syscolumns - WHERE id = object_id('{$table_name}') - AND name = '{$column_name}')) - IF @drop_default_name <> '' - BEGIN - SET @cmd = 'ALTER TABLE [{$table_name}] DROP CONSTRAINT [' + @drop_default_name + ']' - EXEC(@cmd) - END - SET @cmd = 'ALTER TABLE [{$table_name}] ADD CONSTRAINT [DF_{$table_name}_{$column_name}_1] {$column_data['default']} FOR [{$column_name}]' - EXEC(@cmd)"; - } - break; - - case 'mysql_40': - case 'mysql_41': - $statements[] = 'ALTER TABLE `' . $table_name . '` CHANGE `' . $column_name . '` `' . $column_name . '` ' . $column_data['column_type_sql']; - break; - - case 'oracle': - $statements[] = 'ALTER TABLE ' . $table_name . ' MODIFY ' . $column_name . ' ' . $column_data['column_type_sql']; - break; - - case 'postgres': - $sql = 'ALTER TABLE ' . $table_name . ' '; - - $sql_array = array(); - $sql_array[] = 'ALTER COLUMN ' . $column_name . ' TYPE ' . $column_data['column_type']; - - if (isset($column_data['null'])) - { - if ($column_data['null'] == 'NOT NULL') - { - $sql_array[] = 'ALTER COLUMN ' . $column_name . ' SET NOT NULL'; - } - else if ($column_data['null'] == 'NULL') - { - $sql_array[] = 'ALTER COLUMN ' . $column_name . ' DROP NOT NULL'; - } - } - - if (isset($column_data['default'])) - { - $sql_array[] = 'ALTER COLUMN ' . $column_name . ' SET DEFAULT ' . $column_data['default']; - } - - // we don't want to double up on constraints if we change different number data types - if (isset($column_data['constraint'])) - { - $constraint_sql = "SELECT consrc as constraint_data - FROM pg_constraint, pg_class bc - WHERE conrelid = bc.oid - AND bc.relname = '{$table_name}' - AND NOT EXISTS ( - SELECT * - FROM pg_constraint as c, pg_inherits as i - WHERE i.inhrelid = pg_constraint.conrelid - AND c.conname = pg_constraint.conname - AND c.consrc = pg_constraint.consrc - AND c.conrelid = i.inhparent - )"; - - $constraint_exists = false; - - $result = $this->db->sql_query($constraint_sql); - while ($row = $this->db->sql_fetchrow($result)) - { - if (trim($row['constraint_data']) == trim($column_data['constraint'])) - { - $constraint_exists = true; - break; - } - } - $this->db->sql_freeresult($result); - - if (!$constraint_exists) - { - $sql_array[] = 'ADD ' . $column_data['constraint']; - } - } - - $sql .= implode(', ', $sql_array); - - $statements[] = $sql; - break; - - case 'sqlite': - - if ($inline && $this->return_statements) - { - return $column_name . ' ' . $column_data['column_type_sql']; - } - - $sql = "SELECT sql - FROM sqlite_master - WHERE type = 'table' - AND name = '{$table_name}' - ORDER BY type DESC, name;"; - $result = $this->db->sql_query($sql); - - if (!$result) - { - break; - } - - $row = $this->db->sql_fetchrow($result); - $this->db->sql_freeresult($result); - - $statements[] = 'begin'; - - // Create a temp table and populate it, destroy the existing one - $statements[] = preg_replace('#CREATE\s+TABLE\s+"?' . $table_name . '"?#i', 'CREATE TEMPORARY TABLE ' . $table_name . '_temp', $row['sql']); - $statements[] = 'INSERT INTO ' . $table_name . '_temp SELECT * FROM ' . $table_name; - $statements[] = 'DROP TABLE ' . $table_name; - - preg_match('#\((.*)\)#s', $row['sql'], $matches); - - $new_table_cols = trim($matches[1]); - $old_table_cols = preg_split('/,(?![\s\w]+\))/m', $new_table_cols); - $column_list = array(); - - foreach ($old_table_cols as $key => $declaration) - { - $entities = preg_split('#\s+#', trim($declaration)); - $column_list[] = $entities[0]; - if ($entities[0] == $column_name) - { - $old_table_cols[$key] = $column_name . ' ' . $column_data['column_type_sql']; - } - } - - $columns = implode(',', $column_list); - - // create a new table and fill it up. destroy the temp one - $statements[] = 'CREATE TABLE ' . $table_name . ' (' . implode(',', $old_table_cols) . ');'; - $statements[] = 'INSERT INTO ' . $table_name . ' (' . $columns . ') SELECT ' . $columns . ' FROM ' . $table_name . '_temp;'; - $statements[] = 'DROP TABLE ' . $table_name . '_temp'; - - $statements[] = 'commit'; - - break; - } - - return $this->_sql_run_sql($statements); - } -} diff --git a/phpBB/phpbb/db/tools.php b/phpBB/phpbb/db/tools.php new file mode 100644 index 0000000000..492284ffcd --- /dev/null +++ b/phpBB/phpbb/db/tools.php @@ -0,0 +1,2486 @@ + array( + 'INT:' => 'int(%d)', + 'BINT' => 'bigint(20)', + 'UINT' => 'mediumint(8) UNSIGNED', + 'UINT:' => 'int(%d) UNSIGNED', + 'TINT:' => 'tinyint(%d)', + 'USINT' => 'smallint(4) UNSIGNED', + 'BOOL' => 'tinyint(1) UNSIGNED', + 'VCHAR' => 'varchar(255)', + 'VCHAR:' => 'varchar(%d)', + 'CHAR:' => 'char(%d)', + 'XSTEXT' => 'text', + 'XSTEXT_UNI'=> 'varchar(100)', + 'STEXT' => 'text', + 'STEXT_UNI' => 'varchar(255)', + 'TEXT' => 'text', + 'TEXT_UNI' => 'text', + 'MTEXT' => 'mediumtext', + 'MTEXT_UNI' => 'mediumtext', + 'TIMESTAMP' => 'int(11) UNSIGNED', + 'DECIMAL' => 'decimal(5,2)', + 'DECIMAL:' => 'decimal(%d,2)', + 'PDECIMAL' => 'decimal(6,3)', + 'PDECIMAL:' => 'decimal(%d,3)', + 'VCHAR_UNI' => 'varchar(255)', + 'VCHAR_UNI:'=> 'varchar(%d)', + 'VCHAR_CI' => 'varchar(255)', + 'VARBINARY' => 'varbinary(255)', + ), + + 'mysql_40' => array( + 'INT:' => 'int(%d)', + 'BINT' => 'bigint(20)', + 'UINT' => 'mediumint(8) UNSIGNED', + 'UINT:' => 'int(%d) UNSIGNED', + 'TINT:' => 'tinyint(%d)', + 'USINT' => 'smallint(4) UNSIGNED', + 'BOOL' => 'tinyint(1) UNSIGNED', + 'VCHAR' => 'varbinary(255)', + 'VCHAR:' => 'varbinary(%d)', + 'CHAR:' => 'binary(%d)', + 'XSTEXT' => 'blob', + 'XSTEXT_UNI'=> 'blob', + 'STEXT' => 'blob', + 'STEXT_UNI' => 'blob', + 'TEXT' => 'blob', + 'TEXT_UNI' => 'blob', + 'MTEXT' => 'mediumblob', + 'MTEXT_UNI' => 'mediumblob', + 'TIMESTAMP' => 'int(11) UNSIGNED', + 'DECIMAL' => 'decimal(5,2)', + 'DECIMAL:' => 'decimal(%d,2)', + 'PDECIMAL' => 'decimal(6,3)', + 'PDECIMAL:' => 'decimal(%d,3)', + 'VCHAR_UNI' => 'blob', + 'VCHAR_UNI:'=> array('varbinary(%d)', 'limit' => array('mult', 3, 255, 'blob')), + 'VCHAR_CI' => 'blob', + 'VARBINARY' => 'varbinary(255)', + ), + + 'firebird' => array( + 'INT:' => 'INTEGER', + 'BINT' => 'DOUBLE PRECISION', + 'UINT' => 'INTEGER', + 'UINT:' => 'INTEGER', + 'TINT:' => 'INTEGER', + 'USINT' => 'INTEGER', + 'BOOL' => 'INTEGER', + 'VCHAR' => 'VARCHAR(255) CHARACTER SET NONE', + 'VCHAR:' => 'VARCHAR(%d) CHARACTER SET NONE', + 'CHAR:' => 'CHAR(%d) CHARACTER SET NONE', + 'XSTEXT' => 'BLOB SUB_TYPE TEXT CHARACTER SET NONE', + 'STEXT' => 'BLOB SUB_TYPE TEXT CHARACTER SET NONE', + 'TEXT' => 'BLOB SUB_TYPE TEXT CHARACTER SET NONE', + 'MTEXT' => 'BLOB SUB_TYPE TEXT CHARACTER SET NONE', + 'XSTEXT_UNI'=> 'VARCHAR(100) CHARACTER SET UTF8', + 'STEXT_UNI' => 'VARCHAR(255) CHARACTER SET UTF8', + 'TEXT_UNI' => 'BLOB SUB_TYPE TEXT CHARACTER SET UTF8', + 'MTEXT_UNI' => 'BLOB SUB_TYPE TEXT CHARACTER SET UTF8', + 'TIMESTAMP' => 'INTEGER', + 'DECIMAL' => 'DOUBLE PRECISION', + 'DECIMAL:' => 'DOUBLE PRECISION', + 'PDECIMAL' => 'DOUBLE PRECISION', + 'PDECIMAL:' => 'DOUBLE PRECISION', + 'VCHAR_UNI' => 'VARCHAR(255) CHARACTER SET UTF8', + 'VCHAR_UNI:'=> 'VARCHAR(%d) CHARACTER SET UTF8', + 'VCHAR_CI' => 'VARCHAR(255) CHARACTER SET UTF8', + 'VARBINARY' => 'CHAR(255) CHARACTER SET NONE', + ), + + 'mssql' => array( + 'INT:' => '[int]', + 'BINT' => '[float]', + 'UINT' => '[int]', + 'UINT:' => '[int]', + 'TINT:' => '[int]', + 'USINT' => '[int]', + 'BOOL' => '[int]', + 'VCHAR' => '[varchar] (255)', + 'VCHAR:' => '[varchar] (%d)', + 'CHAR:' => '[char] (%d)', + 'XSTEXT' => '[varchar] (1000)', + 'STEXT' => '[varchar] (3000)', + 'TEXT' => '[varchar] (8000)', + 'MTEXT' => '[text]', + 'XSTEXT_UNI'=> '[varchar] (100)', + 'STEXT_UNI' => '[varchar] (255)', + 'TEXT_UNI' => '[varchar] (4000)', + 'MTEXT_UNI' => '[text]', + 'TIMESTAMP' => '[int]', + 'DECIMAL' => '[float]', + 'DECIMAL:' => '[float]', + 'PDECIMAL' => '[float]', + 'PDECIMAL:' => '[float]', + 'VCHAR_UNI' => '[varchar] (255)', + 'VCHAR_UNI:'=> '[varchar] (%d)', + 'VCHAR_CI' => '[varchar] (255)', + 'VARBINARY' => '[varchar] (255)', + ), + + 'mssqlnative' => array( + 'INT:' => '[int]', + 'BINT' => '[float]', + 'UINT' => '[int]', + 'UINT:' => '[int]', + 'TINT:' => '[int]', + 'USINT' => '[int]', + 'BOOL' => '[int]', + 'VCHAR' => '[varchar] (255)', + 'VCHAR:' => '[varchar] (%d)', + 'CHAR:' => '[char] (%d)', + 'XSTEXT' => '[varchar] (1000)', + 'STEXT' => '[varchar] (3000)', + 'TEXT' => '[varchar] (8000)', + 'MTEXT' => '[text]', + 'XSTEXT_UNI'=> '[varchar] (100)', + 'STEXT_UNI' => '[varchar] (255)', + 'TEXT_UNI' => '[varchar] (4000)', + 'MTEXT_UNI' => '[text]', + 'TIMESTAMP' => '[int]', + 'DECIMAL' => '[float]', + 'DECIMAL:' => '[float]', + 'PDECIMAL' => '[float]', + 'PDECIMAL:' => '[float]', + 'VCHAR_UNI' => '[varchar] (255)', + 'VCHAR_UNI:'=> '[varchar] (%d)', + 'VCHAR_CI' => '[varchar] (255)', + 'VARBINARY' => '[varchar] (255)', + ), + + 'oracle' => array( + 'INT:' => 'number(%d)', + 'BINT' => 'number(20)', + 'UINT' => 'number(8)', + 'UINT:' => 'number(%d)', + 'TINT:' => 'number(%d)', + 'USINT' => 'number(4)', + 'BOOL' => 'number(1)', + 'VCHAR' => 'varchar2(255)', + 'VCHAR:' => 'varchar2(%d)', + 'CHAR:' => 'char(%d)', + 'XSTEXT' => 'varchar2(1000)', + 'STEXT' => 'varchar2(3000)', + 'TEXT' => 'clob', + 'MTEXT' => 'clob', + 'XSTEXT_UNI'=> 'varchar2(300)', + 'STEXT_UNI' => 'varchar2(765)', + 'TEXT_UNI' => 'clob', + 'MTEXT_UNI' => 'clob', + 'TIMESTAMP' => 'number(11)', + 'DECIMAL' => 'number(5, 2)', + 'DECIMAL:' => 'number(%d, 2)', + 'PDECIMAL' => 'number(6, 3)', + 'PDECIMAL:' => 'number(%d, 3)', + 'VCHAR_UNI' => 'varchar2(765)', + 'VCHAR_UNI:'=> array('varchar2(%d)', 'limit' => array('mult', 3, 765, 'clob')), + 'VCHAR_CI' => 'varchar2(255)', + 'VARBINARY' => 'raw(255)', + ), + + 'sqlite' => array( + 'INT:' => 'int(%d)', + 'BINT' => 'bigint(20)', + 'UINT' => 'INTEGER UNSIGNED', //'mediumint(8) UNSIGNED', + 'UINT:' => 'INTEGER UNSIGNED', // 'int(%d) UNSIGNED', + 'TINT:' => 'tinyint(%d)', + 'USINT' => 'INTEGER UNSIGNED', //'mediumint(4) UNSIGNED', + 'BOOL' => 'INTEGER UNSIGNED', //'tinyint(1) UNSIGNED', + 'VCHAR' => 'varchar(255)', + 'VCHAR:' => 'varchar(%d)', + 'CHAR:' => 'char(%d)', + 'XSTEXT' => 'text(65535)', + 'STEXT' => 'text(65535)', + 'TEXT' => 'text(65535)', + 'MTEXT' => 'mediumtext(16777215)', + 'XSTEXT_UNI'=> 'text(65535)', + 'STEXT_UNI' => 'text(65535)', + 'TEXT_UNI' => 'text(65535)', + 'MTEXT_UNI' => 'mediumtext(16777215)', + 'TIMESTAMP' => 'INTEGER UNSIGNED', //'int(11) UNSIGNED', + 'DECIMAL' => 'decimal(5,2)', + 'DECIMAL:' => 'decimal(%d,2)', + 'PDECIMAL' => 'decimal(6,3)', + 'PDECIMAL:' => 'decimal(%d,3)', + 'VCHAR_UNI' => 'varchar(255)', + 'VCHAR_UNI:'=> 'varchar(%d)', + 'VCHAR_CI' => 'varchar(255)', + 'VARBINARY' => 'blob', + ), + + 'postgres' => array( + 'INT:' => 'INT4', + 'BINT' => 'INT8', + 'UINT' => 'INT4', // unsigned + 'UINT:' => 'INT4', // unsigned + 'USINT' => 'INT2', // unsigned + 'BOOL' => 'INT2', // unsigned + 'TINT:' => 'INT2', + 'VCHAR' => 'varchar(255)', + 'VCHAR:' => 'varchar(%d)', + 'CHAR:' => 'char(%d)', + 'XSTEXT' => 'varchar(1000)', + 'STEXT' => 'varchar(3000)', + 'TEXT' => 'varchar(8000)', + 'MTEXT' => 'TEXT', + 'XSTEXT_UNI'=> 'varchar(100)', + 'STEXT_UNI' => 'varchar(255)', + 'TEXT_UNI' => 'varchar(4000)', + 'MTEXT_UNI' => 'TEXT', + 'TIMESTAMP' => 'INT4', // unsigned + 'DECIMAL' => 'decimal(5,2)', + 'DECIMAL:' => 'decimal(%d,2)', + 'PDECIMAL' => 'decimal(6,3)', + 'PDECIMAL:' => 'decimal(%d,3)', + 'VCHAR_UNI' => 'varchar(255)', + 'VCHAR_UNI:'=> 'varchar(%d)', + 'VCHAR_CI' => 'varchar_ci', + 'VARBINARY' => 'bytea', + ), + ); + + /** + * A list of types being unsigned for better reference in some db's + * @var array + */ + var $unsigned_types = array('UINT', 'UINT:', 'USINT', 'BOOL', 'TIMESTAMP'); + + /** + * A list of supported DBMS. We change this class to support more DBMS, the DBMS itself only need to follow some rules. + * @var array + */ + var $supported_dbms = array('firebird', 'mssql', 'mssqlnative', 'mysql_40', 'mysql_41', 'oracle', 'postgres', 'sqlite'); + + /** + * This is set to true if user only wants to return the 'to-be-executed' SQL statement(s) (as an array). + * This mode has no effect on some methods (inserting of data for example). This is expressed within the methods command. + */ + var $return_statements = false; + + /** + * Constructor. Set DB Object and set {@link $return_statements return_statements}. + * + * @param phpbb_db_driver $db Database connection + * @param bool $return_statements True if only statements should be returned and no SQL being executed + */ + public function __construct(phpbb_db_driver $db, $return_statements = false) + { + $this->db = $db; + $this->return_statements = $return_statements; + + // Determine mapping database type + switch ($this->db->sql_layer) + { + case 'mysql': + $this->sql_layer = 'mysql_40'; + break; + + case 'mysql4': + if (version_compare($this->db->sql_server_info(true), '4.1.3', '>=')) + { + $this->sql_layer = 'mysql_41'; + } + else + { + $this->sql_layer = 'mysql_40'; + } + break; + + case 'mysqli': + $this->sql_layer = 'mysql_41'; + break; + + case 'mssql': + case 'mssql_odbc': + $this->sql_layer = 'mssql'; + break; + + case 'mssqlnative': + $this->sql_layer = 'mssqlnative'; + break; + + default: + $this->sql_layer = $this->db->sql_layer; + break; + } + } + + /** + * Setter for {@link $return_statements return_statements}. + * + * @param bool $return_statements True if SQL should not be executed but returned as strings + * @return null + */ + public function set_return_statements($return_statements) + { + $this->return_statements = $return_statements; + } + + /** + * Gets a list of tables in the database. + * + * @return array Array of table names (all lower case) + */ + function sql_list_tables() + { + switch ($this->db->sql_layer) + { + case 'mysql': + case 'mysql4': + case 'mysqli': + $sql = 'SHOW TABLES'; + break; + + case 'sqlite': + $sql = 'SELECT name + FROM sqlite_master + WHERE type = "table"'; + break; + + case 'mssql': + case 'mssql_odbc': + case 'mssqlnative': + $sql = "SELECT name + FROM sysobjects + WHERE type='U'"; + break; + + case 'postgres': + $sql = 'SELECT relname + FROM pg_stat_user_tables'; + break; + + case 'firebird': + $sql = 'SELECT rdb$relation_name + FROM rdb$relations + WHERE rdb$view_source is null + AND rdb$system_flag = 0'; + break; + + case 'oracle': + $sql = 'SELECT table_name + FROM USER_TABLES'; + break; + } + + $result = $this->db->sql_query($sql); + + $tables = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $name = current($row); + $tables[$name] = $name; + } + $this->db->sql_freeresult($result); + + return $tables; + } + + /** + * Check if table exists + * + * + * @param string $table_name The table name to check for + * @return bool true if table exists, else false + */ + function sql_table_exists($table_name) + { + $this->db->sql_return_on_error(true); + $result = $this->db->sql_query_limit('SELECT * FROM ' . $table_name, 1); + $this->db->sql_return_on_error(false); + + if ($result) + { + $this->db->sql_freeresult($result); + return true; + } + + return false; + } + + /** + * Create SQL Table + * + * @param string $table_name The table name to create + * @param array $table_data Array containing table data. + * @return array Statements if $return_statements is true. + */ + function sql_create_table($table_name, $table_data) + { + // holds the DDL for a column + $columns = $statements = array(); + + if ($this->sql_table_exists($table_name)) + { + return $this->_sql_run_sql($statements); + } + + // Begin transaction + $statements[] = 'begin'; + + // Determine if we have created a PRIMARY KEY in the earliest + $primary_key_gen = false; + + // Determine if the table must be created with TEXTIMAGE + $create_textimage = false; + + // Determine if the table requires a sequence + $create_sequence = false; + + // Begin table sql statement + switch ($this->sql_layer) + { + case 'mssql': + case 'mssqlnative': + $table_sql = 'CREATE TABLE [' . $table_name . '] (' . "\n"; + break; + + default: + $table_sql = 'CREATE TABLE ' . $table_name . ' (' . "\n"; + break; + } + + // Iterate through the columns to create a table + foreach ($table_data['COLUMNS'] as $column_name => $column_data) + { + // here lies an array, filled with information compiled on the column's data + $prepared_column = $this->sql_prepare_column_data($table_name, $column_name, $column_data); + + if (isset($prepared_column['auto_increment']) && strlen($column_name) > 26) // "${column_name}_gen" + { + trigger_error("Index name '${column_name}_gen' on table '$table_name' is too long. The maximum auto increment column length is 26 characters.", E_USER_ERROR); + } + + // here we add the definition of the new column to the list of columns + switch ($this->sql_layer) + { + case 'mssql': + case 'mssqlnative': + $columns[] = "\t [{$column_name}] " . $prepared_column['column_type_sql_default']; + break; + + default: + $columns[] = "\t {$column_name} " . $prepared_column['column_type_sql']; + break; + } + + // see if we have found a primary key set due to a column definition if we have found it, we can stop looking + if (!$primary_key_gen) + { + $primary_key_gen = isset($prepared_column['primary_key_set']) && $prepared_column['primary_key_set']; + } + + // create textimage DDL based off of the existance of certain column types + if (!$create_textimage) + { + $create_textimage = isset($prepared_column['textimage']) && $prepared_column['textimage']; + } + + // create sequence DDL based off of the existance of auto incrementing columns + if (!$create_sequence && isset($prepared_column['auto_increment']) && $prepared_column['auto_increment']) + { + $create_sequence = $column_name; + } + } + + // this makes up all the columns in the create table statement + $table_sql .= implode(",\n", $columns); + + // Close the table for two DBMS and add to the statements + switch ($this->sql_layer) + { + case 'firebird': + $table_sql .= "\n);"; + $statements[] = $table_sql; + break; + + case 'mssql': + case 'mssqlnative': + $table_sql .= "\n) ON [PRIMARY]" . (($create_textimage) ? ' TEXTIMAGE_ON [PRIMARY]' : ''); + $statements[] = $table_sql; + break; + } + + // we have yet to create a primary key for this table, + // this means that we can add the one we really wanted instead + if (!$primary_key_gen) + { + // Write primary key + if (isset($table_data['PRIMARY_KEY'])) + { + if (!is_array($table_data['PRIMARY_KEY'])) + { + $table_data['PRIMARY_KEY'] = array($table_data['PRIMARY_KEY']); + } + + switch ($this->sql_layer) + { + case 'mysql_40': + case 'mysql_41': + case 'postgres': + case 'sqlite': + $table_sql .= ",\n\t PRIMARY KEY (" . implode(', ', $table_data['PRIMARY_KEY']) . ')'; + break; + + case 'firebird': + case 'mssql': + case 'mssqlnative': + // We need the data here + $old_return_statements = $this->return_statements; + $this->return_statements = true; + + $primary_key_stmts = $this->sql_create_primary_key($table_name, $table_data['PRIMARY_KEY']); + foreach ($primary_key_stmts as $pk_stmt) + { + $statements[] = $pk_stmt; + } + + $this->return_statements = $old_return_statements; + break; + + case 'oracle': + $table_sql .= ",\n\t CONSTRAINT pk_{$table_name} PRIMARY KEY (" . implode(', ', $table_data['PRIMARY_KEY']) . ')'; + break; + } + } + } + + // close the table + switch ($this->sql_layer) + { + case 'mysql_41': + // make sure the table is in UTF-8 mode + $table_sql .= "\n) CHARACTER SET `utf8` COLLATE `utf8_bin`;"; + $statements[] = $table_sql; + break; + + case 'mysql_40': + case 'sqlite': + $table_sql .= "\n);"; + $statements[] = $table_sql; + break; + + case 'postgres': + // do we need to add a sequence for auto incrementing columns? + if ($create_sequence) + { + $statements[] = "CREATE SEQUENCE {$table_name}_seq;"; + } + + $table_sql .= "\n);"; + $statements[] = $table_sql; + break; + + case 'oracle': + $table_sql .= "\n)"; + $statements[] = $table_sql; + + // do we need to add a sequence and a tigger for auto incrementing columns? + if ($create_sequence) + { + // create the actual sequence + $statements[] = "CREATE SEQUENCE {$table_name}_seq"; + + // the trigger is the mechanism by which we increment the counter + $trigger = "CREATE OR REPLACE TRIGGER t_{$table_name}\n"; + $trigger .= "BEFORE INSERT ON {$table_name}\n"; + $trigger .= "FOR EACH ROW WHEN (\n"; + $trigger .= "\tnew.{$create_sequence} IS NULL OR new.{$create_sequence} = 0\n"; + $trigger .= ")\n"; + $trigger .= "BEGIN\n"; + $trigger .= "\tSELECT {$table_name}_seq.nextval\n"; + $trigger .= "\tINTO :new.{$create_sequence}\n"; + $trigger .= "\tFROM dual;\n"; + $trigger .= "END;"; + + $statements[] = $trigger; + } + break; + + case 'firebird': + if ($create_sequence) + { + $statements[] = "CREATE GENERATOR {$table_name}_gen;"; + $statements[] = "SET GENERATOR {$table_name}_gen TO 0;"; + + $trigger = "CREATE TRIGGER t_$table_name FOR $table_name\n"; + $trigger .= "BEFORE INSERT\nAS\nBEGIN\n"; + $trigger .= "\tNEW.{$create_sequence} = GEN_ID({$table_name}_gen, 1);\nEND;"; + $statements[] = $trigger; + } + break; + } + + // Write Keys + if (isset($table_data['KEYS'])) + { + foreach ($table_data['KEYS'] as $key_name => $key_data) + { + if (!is_array($key_data[1])) + { + $key_data[1] = array($key_data[1]); + } + + $old_return_statements = $this->return_statements; + $this->return_statements = true; + + $key_stmts = ($key_data[0] == 'UNIQUE') ? $this->sql_create_unique_index($table_name, $key_name, $key_data[1]) : $this->sql_create_index($table_name, $key_name, $key_data[1]); + + foreach ($key_stmts as $key_stmt) + { + $statements[] = $key_stmt; + } + + $this->return_statements = $old_return_statements; + } + } + + // Commit Transaction + $statements[] = 'commit'; + + return $this->_sql_run_sql($statements); + } + + /** + * Handle passed database update array. + * Expected structure... + * Key being one of the following + * drop_tables: Drop tables + * add_tables: Add tables + * change_columns: Column changes (only type, not name) + * add_columns: Add columns to a table + * drop_keys: Dropping keys + * drop_columns: Removing/Dropping columns + * add_primary_keys: adding primary keys + * add_unique_index: adding an unique index + * add_index: adding an index (can be column:index_size if you need to provide size) + * + * The values are in this format: + * {TABLE NAME} => array( + * {COLUMN NAME} => array({COLUMN TYPE}, {DEFAULT VALUE}, {OPTIONAL VARIABLES}), + * {KEY/INDEX NAME} => array({COLUMN NAMES}), + * ) + * + * For more information have a look at /develop/create_schema_files.php (only available through SVN) + */ + function perform_schema_changes($schema_changes) + { + if (empty($schema_changes)) + { + return; + } + + $statements = array(); + $sqlite = false; + + // For SQLite we need to perform the schema changes in a much more different way + if ($this->db->sql_layer == 'sqlite' && $this->return_statements) + { + $sqlite_data = array(); + $sqlite = true; + } + + // Drop tables? + if (!empty($schema_changes['drop_tables'])) + { + foreach ($schema_changes['drop_tables'] as $table) + { + // only drop table if it exists + if ($this->sql_table_exists($table)) + { + $result = $this->sql_table_drop($table); + if ($this->return_statements) + { + $statements = array_merge($statements, $result); + } + } + } + } + + // Add tables? + if (!empty($schema_changes['add_tables'])) + { + foreach ($schema_changes['add_tables'] as $table => $table_data) + { + $result = $this->sql_create_table($table, $table_data); + if ($this->return_statements) + { + $statements = array_merge($statements, $result); + } + } + } + + // Change columns? + if (!empty($schema_changes['change_columns'])) + { + foreach ($schema_changes['change_columns'] as $table => $columns) + { + foreach ($columns as $column_name => $column_data) + { + // If the column exists we change it, else we add it ;) + if ($column_exists = $this->sql_column_exists($table, $column_name)) + { + $result = $this->sql_column_change($table, $column_name, $column_data, true); + } + else + { + $result = $this->sql_column_add($table, $column_name, $column_data, true); + } + + if ($sqlite) + { + if ($column_exists) + { + $sqlite_data[$table]['change_columns'][] = $result; + } + else + { + $sqlite_data[$table]['add_columns'][] = $result; + } + } + else if ($this->return_statements) + { + $statements = array_merge($statements, $result); + } + } + } + } + + // Add columns? + if (!empty($schema_changes['add_columns'])) + { + foreach ($schema_changes['add_columns'] as $table => $columns) + { + foreach ($columns as $column_name => $column_data) + { + // Only add the column if it does not exist yet + if ($column_exists = $this->sql_column_exists($table, $column_name)) + { + continue; + // This is commented out here because it can take tremendous time on updates +// $result = $this->sql_column_change($table, $column_name, $column_data, true); + } + else + { + $result = $this->sql_column_add($table, $column_name, $column_data, true); + } + + if ($sqlite) + { + if ($column_exists) + { + continue; +// $sqlite_data[$table]['change_columns'][] = $result; + } + else + { + $sqlite_data[$table]['add_columns'][] = $result; + } + } + else if ($this->return_statements) + { + $statements = array_merge($statements, $result); + } + } + } + } + + // Remove keys? + if (!empty($schema_changes['drop_keys'])) + { + foreach ($schema_changes['drop_keys'] as $table => $indexes) + { + foreach ($indexes as $index_name) + { + if (!$this->sql_index_exists($table, $index_name)) + { + continue; + } + + $result = $this->sql_index_drop($table, $index_name); + + if ($this->return_statements) + { + $statements = array_merge($statements, $result); + } + } + } + } + + // Drop columns? + if (!empty($schema_changes['drop_columns'])) + { + foreach ($schema_changes['drop_columns'] as $table => $columns) + { + foreach ($columns as $column) + { + // Only remove the column if it exists... + if ($this->sql_column_exists($table, $column)) + { + $result = $this->sql_column_remove($table, $column, true); + + if ($sqlite) + { + $sqlite_data[$table]['drop_columns'][] = $result; + } + else if ($this->return_statements) + { + $statements = array_merge($statements, $result); + } + } + } + } + } + + // Add primary keys? + if (!empty($schema_changes['add_primary_keys'])) + { + foreach ($schema_changes['add_primary_keys'] as $table => $columns) + { + $result = $this->sql_create_primary_key($table, $columns, true); + + if ($sqlite) + { + $sqlite_data[$table]['primary_key'] = $result; + } + else if ($this->return_statements) + { + $statements = array_merge($statements, $result); + } + } + } + + // Add unqiue indexes? + if (!empty($schema_changes['add_unique_index'])) + { + foreach ($schema_changes['add_unique_index'] as $table => $index_array) + { + foreach ($index_array as $index_name => $column) + { + if ($this->sql_unique_index_exists($table, $index_name)) + { + continue; + } + + $result = $this->sql_create_unique_index($table, $index_name, $column); + + if ($this->return_statements) + { + $statements = array_merge($statements, $result); + } + } + } + } + + // Add indexes? + if (!empty($schema_changes['add_index'])) + { + foreach ($schema_changes['add_index'] as $table => $index_array) + { + foreach ($index_array as $index_name => $column) + { + if ($this->sql_index_exists($table, $index_name)) + { + continue; + } + + $result = $this->sql_create_index($table, $index_name, $column); + + if ($this->return_statements) + { + $statements = array_merge($statements, $result); + } + } + } + } + + if ($sqlite) + { + foreach ($sqlite_data as $table_name => $sql_schema_changes) + { + // Create temporary table with original data + $statements[] = 'begin'; + + $sql = "SELECT sql + FROM sqlite_master + WHERE type = 'table' + AND name = '{$table_name}' + ORDER BY type DESC, name;"; + $result = $this->db->sql_query($sql); + + if (!$result) + { + continue; + } + + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + // Create a backup table and populate it, destroy the existing one + $statements[] = preg_replace('#CREATE\s+TABLE\s+"?' . $table_name . '"?#i', 'CREATE TEMPORARY TABLE ' . $table_name . '_temp', $row['sql']); + $statements[] = 'INSERT INTO ' . $table_name . '_temp SELECT * FROM ' . $table_name; + $statements[] = 'DROP TABLE ' . $table_name; + + // Get the columns... + preg_match('#\((.*)\)#s', $row['sql'], $matches); + + $plain_table_cols = trim($matches[1]); + $new_table_cols = preg_split('/,(?![\s\w]+\))/m', $plain_table_cols); + $column_list = array(); + + foreach ($new_table_cols as $declaration) + { + $entities = preg_split('#\s+#', trim($declaration)); + if ($entities[0] == 'PRIMARY') + { + continue; + } + $column_list[] = $entities[0]; + } + + // note down the primary key notation because sqlite only supports adding it to the end for the new table + $primary_key = false; + $_new_cols = array(); + + foreach ($new_table_cols as $key => $declaration) + { + $entities = preg_split('#\s+#', trim($declaration)); + if ($entities[0] == 'PRIMARY') + { + $primary_key = $declaration; + continue; + } + $_new_cols[] = $declaration; + } + + $new_table_cols = $_new_cols; + + // First of all... change columns + if (!empty($sql_schema_changes['change_columns'])) + { + foreach ($sql_schema_changes['change_columns'] as $column_sql) + { + foreach ($new_table_cols as $key => $declaration) + { + $entities = preg_split('#\s+#', trim($declaration)); + if (strpos($column_sql, $entities[0] . ' ') === 0) + { + $new_table_cols[$key] = $column_sql; + } + } + } + } + + if (!empty($sql_schema_changes['add_columns'])) + { + foreach ($sql_schema_changes['add_columns'] as $column_sql) + { + $new_table_cols[] = $column_sql; + } + } + + // Now drop them... + if (!empty($sql_schema_changes['drop_columns'])) + { + foreach ($sql_schema_changes['drop_columns'] as $column_name) + { + // Remove from column list... + $new_column_list = array(); + foreach ($column_list as $key => $value) + { + if ($value === $column_name) + { + continue; + } + + $new_column_list[] = $value; + } + + $column_list = $new_column_list; + + // Remove from table... + $_new_cols = array(); + foreach ($new_table_cols as $key => $declaration) + { + $entities = preg_split('#\s+#', trim($declaration)); + if (strpos($column_name . ' ', $entities[0] . ' ') === 0) + { + continue; + } + $_new_cols[] = $declaration; + } + $new_table_cols = $_new_cols; + } + } + + // Primary key... + if (!empty($sql_schema_changes['primary_key'])) + { + $new_table_cols[] = 'PRIMARY KEY (' . implode(', ', $sql_schema_changes['primary_key']) . ')'; + } + // Add a new one or the old primary key + else if ($primary_key !== false) + { + $new_table_cols[] = $primary_key; + } + + $columns = implode(',', $column_list); + + // create a new table and fill it up. destroy the temp one + $statements[] = 'CREATE TABLE ' . $table_name . ' (' . implode(',', $new_table_cols) . ');'; + $statements[] = 'INSERT INTO ' . $table_name . ' (' . $columns . ') SELECT ' . $columns . ' FROM ' . $table_name . '_temp;'; + $statements[] = 'DROP TABLE ' . $table_name . '_temp'; + + $statements[] = 'commit'; + } + } + + if ($this->return_statements) + { + return $statements; + } + } + + /** + * Gets a list of columns of a table. + * + * @param string $table Table name + * + * @return array Array of column names (all lower case) + */ + function sql_list_columns($table) + { + $columns = array(); + + switch ($this->sql_layer) + { + case 'mysql_40': + case 'mysql_41': + $sql = "SHOW COLUMNS FROM $table"; + break; + + // PostgreSQL has a way of doing this in a much simpler way but would + // not allow us to support all versions of PostgreSQL + case 'postgres': + $sql = "SELECT a.attname + FROM pg_class c, pg_attribute a + WHERE c.relname = '{$table}' + AND a.attnum > 0 + AND a.attrelid = c.oid"; + break; + + // same deal with PostgreSQL, we must perform more complex operations than + // we technically could + case 'mssql': + case 'mssqlnative': + $sql = "SELECT c.name + FROM syscolumns c + LEFT JOIN sysobjects o ON c.id = o.id + WHERE o.name = '{$table}'"; + break; + + case 'oracle': + $sql = "SELECT column_name + FROM user_tab_columns + WHERE LOWER(table_name) = '" . strtolower($table) . "'"; + break; + + case 'firebird': + $sql = "SELECT RDB\$FIELD_NAME as FNAME + FROM RDB\$RELATION_FIELDS + WHERE RDB\$RELATION_NAME = '" . strtoupper($table) . "'"; + break; + + case 'sqlite': + $sql = "SELECT sql + FROM sqlite_master + WHERE type = 'table' + AND name = '{$table}'"; + + $result = $this->db->sql_query($sql); + + if (!$result) + { + return false; + } + + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + preg_match('#\((.*)\)#s', $row['sql'], $matches); + + $cols = trim($matches[1]); + $col_array = preg_split('/,(?![\s\w]+\))/m', $cols); + + foreach ($col_array as $declaration) + { + $entities = preg_split('#\s+#', trim($declaration)); + if ($entities[0] == 'PRIMARY') + { + continue; + } + + $column = strtolower($entities[0]); + $columns[$column] = $column; + } + + return $columns; + break; + } + + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $column = strtolower(current($row)); + $columns[$column] = $column; + } + $this->db->sql_freeresult($result); + + return $columns; + } + + /** + * Check whether a specified column exist in a table + * + * @param string $table Table to check + * @param string $column_name Column to check + * + * @return bool True if column exists, false otherwise + */ + function sql_column_exists($table, $column_name) + { + $columns = $this->sql_list_columns($table); + + return isset($columns[$column_name]); + } + + /** + * Check if a specified index exists in table. Does not return PRIMARY KEY and UNIQUE indexes. + * + * @param string $table_name Table to check the index at + * @param string $index_name The index name to check + * + * @return bool True if index exists, else false + */ + function sql_index_exists($table_name, $index_name) + { + if ($this->sql_layer == 'mssql' || $this->sql_layer == 'mssqlnative') + { + $sql = "EXEC sp_statistics '$table_name'"; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + if ($row['TYPE'] == 3) + { + if (strtolower($row['INDEX_NAME']) == strtolower($index_name)) + { + $this->db->sql_freeresult($result); + return true; + } + } + } + $this->db->sql_freeresult($result); + + return false; + } + + switch ($this->sql_layer) + { + case 'firebird': + $sql = "SELECT LOWER(RDB\$INDEX_NAME) as index_name + FROM RDB\$INDICES + WHERE RDB\$RELATION_NAME = '" . strtoupper($table_name) . "' + AND RDB\$UNIQUE_FLAG IS NULL + AND RDB\$FOREIGN_KEY IS NULL"; + $col = 'index_name'; + break; + + case 'postgres': + $sql = "SELECT ic.relname as index_name + FROM pg_class bc, pg_class ic, pg_index i + WHERE (bc.oid = i.indrelid) + AND (ic.oid = i.indexrelid) + AND (bc.relname = '" . $table_name . "') + AND (i.indisunique != 't') + AND (i.indisprimary != 't')"; + $col = 'index_name'; + break; + + case 'mysql_40': + case 'mysql_41': + $sql = 'SHOW KEYS + FROM ' . $table_name; + $col = 'Key_name'; + break; + + case 'oracle': + $sql = "SELECT index_name + FROM user_indexes + WHERE table_name = '" . strtoupper($table_name) . "' + AND generated = 'N' + AND uniqueness = 'NONUNIQUE'"; + $col = 'index_name'; + break; + + case 'sqlite': + $sql = "PRAGMA index_list('" . $table_name . "');"; + $col = 'name'; + break; + } + + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + if (($this->sql_layer == 'mysql_40' || $this->sql_layer == 'mysql_41') && !$row['Non_unique']) + { + continue; + } + + // These DBMS prefix index name with the table name + switch ($this->sql_layer) + { + case 'firebird': + case 'oracle': + case 'postgres': + case 'sqlite': + $row[$col] = substr($row[$col], strlen($table_name) + 1); + break; + } + + if (strtolower($row[$col]) == strtolower($index_name)) + { + $this->db->sql_freeresult($result); + return true; + } + } + $this->db->sql_freeresult($result); + + return false; + } + + /** + * Check if a specified index exists in table. Does not return PRIMARY KEY and UNIQUE indexes. + * + * @param string $table_name Table to check the index at + * @param string $index_name The index name to check + * + * @return bool True if index exists, else false + */ + function sql_unique_index_exists($table_name, $index_name) + { + if ($this->sql_layer == 'mssql' || $this->sql_layer == 'mssqlnative') + { + $sql = "EXEC sp_statistics '$table_name'"; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + // Usually NON_UNIQUE is the column we want to check, but we allow for both + if ($row['TYPE'] == 3) + { + if (strtolower($row['INDEX_NAME']) == strtolower($index_name)) + { + $this->db->sql_freeresult($result); + return true; + } + } + } + $this->db->sql_freeresult($result); + return false; + } + + switch ($this->sql_layer) + { + case 'firebird': + $sql = "SELECT LOWER(RDB\$INDEX_NAME) as index_name + FROM RDB\$INDICES + WHERE RDB\$RELATION_NAME = '" . strtoupper($table_name) . "' + AND RDB\$UNIQUE_FLAG IS NOT NULL + AND RDB\$FOREIGN_KEY IS NULL"; + $col = 'index_name'; + break; + + case 'postgres': + $sql = "SELECT ic.relname as index_name, i.indisunique + FROM pg_class bc, pg_class ic, pg_index i + WHERE (bc.oid = i.indrelid) + AND (ic.oid = i.indexrelid) + AND (bc.relname = '" . $table_name . "') + AND (i.indisprimary != 't')"; + $col = 'index_name'; + break; + + case 'mysql_40': + case 'mysql_41': + $sql = 'SHOW KEYS + FROM ' . $table_name; + $col = 'Key_name'; + break; + + case 'oracle': + $sql = "SELECT index_name, table_owner + FROM user_indexes + WHERE table_name = '" . strtoupper($table_name) . "' + AND generated = 'N' + AND uniqueness = 'UNIQUE'"; + $col = 'index_name'; + break; + + case 'sqlite': + $sql = "PRAGMA index_list('" . $table_name . "');"; + $col = 'name'; + break; + } + + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + if (($this->sql_layer == 'mysql_40' || $this->sql_layer == 'mysql_41') && ($row['Non_unique'] || $row[$col] == 'PRIMARY')) + { + continue; + } + + if ($this->sql_layer == 'sqlite' && !$row['unique']) + { + continue; + } + + if ($this->sql_layer == 'postgres' && $row['indisunique'] != 't') + { + continue; + } + + // These DBMS prefix index name with the table name + switch ($this->sql_layer) + { + case 'oracle': + // Two cases here... prefixed with U_[table_owner] and not prefixed with table_name + if (strpos($row[$col], 'U_') === 0) + { + $row[$col] = substr($row[$col], strlen('U_' . $row['table_owner']) + 1); + } + else if (strpos($row[$col], strtoupper($table_name)) === 0) + { + $row[$col] = substr($row[$col], strlen($table_name) + 1); + } + break; + + case 'firebird': + case 'postgres': + case 'sqlite': + $row[$col] = substr($row[$col], strlen($table_name) + 1); + break; + } + + if (strtolower($row[$col]) == strtolower($index_name)) + { + $this->db->sql_freeresult($result); + return true; + } + } + $this->db->sql_freeresult($result); + + return false; + } + + /** + * Private method for performing sql statements (either execute them or return them) + * @access private + */ + function _sql_run_sql($statements) + { + if ($this->return_statements) + { + return $statements; + } + + // We could add error handling here... + foreach ($statements as $sql) + { + if ($sql === 'begin') + { + $this->db->sql_transaction('begin'); + } + else if ($sql === 'commit') + { + $this->db->sql_transaction('commit'); + } + else + { + $this->db->sql_query($sql); + } + } + + return true; + } + + /** + * Function to prepare some column information for better usage + * @access private + */ + function sql_prepare_column_data($table_name, $column_name, $column_data) + { + if (strlen($column_name) > 30) + { + trigger_error("Column name '$column_name' on table '$table_name' is too long. The maximum is 30 characters.", E_USER_ERROR); + } + + // Get type + if (strpos($column_data[0], ':') !== false) + { + list($orig_column_type, $column_length) = explode(':', $column_data[0]); + if (!is_array($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':'])) + { + $column_type = sprintf($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':'], $column_length); + } + else + { + if (isset($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['rule'])) + { + switch ($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['rule'][0]) + { + case 'div': + $column_length /= $this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['rule'][1]; + $column_length = ceil($column_length); + $column_type = sprintf($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':'][0], $column_length); + break; + } + } + + if (isset($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'])) + { + switch ($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'][0]) + { + case 'mult': + $column_length *= $this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'][1]; + if ($column_length > $this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'][2]) + { + $column_type = $this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'][3]; + } + else + { + $column_type = sprintf($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':'][0], $column_length); + } + break; + } + } + } + $orig_column_type .= ':'; + } + else + { + $orig_column_type = $column_data[0]; + $column_type = $this->dbms_type_map[$this->sql_layer][$column_data[0]]; + } + + // Adjust default value if db-dependent specified + if (is_array($column_data[1])) + { + $column_data[1] = (isset($column_data[1][$this->sql_layer])) ? $column_data[1][$this->sql_layer] : $column_data[1]['default']; + } + + $sql = ''; + + $return_array = array(); + + switch ($this->sql_layer) + { + case 'firebird': + $sql .= " {$column_type} "; + $return_array['column_type_sql_type'] = " {$column_type} "; + + if (!is_null($column_data[1])) + { + $sql .= 'DEFAULT ' . ((is_numeric($column_data[1])) ? $column_data[1] : "'{$column_data[1]}'") . ' '; + $return_array['column_type_sql_default'] = ((is_numeric($column_data[1])) ? $column_data[1] : "'{$column_data[1]}'") . ' '; + } + + $sql .= 'NOT NULL'; + + // This is a UNICODE column and thus should be given it's fair share + if (preg_match('/^X?STEXT_UNI|VCHAR_(CI|UNI:?)/', $column_data[0])) + { + $sql .= ' COLLATE UNICODE'; + } + + $return_array['auto_increment'] = false; + if (isset($column_data[2]) && $column_data[2] == 'auto_increment') + { + $return_array['auto_increment'] = true; + } + + break; + + case 'mssql': + case 'mssqlnative': + $sql .= " {$column_type} "; + $sql_default = " {$column_type} "; + + // For adding columns we need the default definition + if (!is_null($column_data[1])) + { + // For hexadecimal values do not use single quotes + if (strpos($column_data[1], '0x') === 0) + { + $return_array['default'] = 'DEFAULT (' . $column_data[1] . ') '; + $sql_default .= $return_array['default']; + } + else + { + $return_array['default'] = 'DEFAULT (' . ((is_numeric($column_data[1])) ? $column_data[1] : "'{$column_data[1]}'") . ') '; + $sql_default .= $return_array['default']; + } + } + + if (isset($column_data[2]) && $column_data[2] == 'auto_increment') + { +// $sql .= 'IDENTITY (1, 1) '; + $sql_default .= 'IDENTITY (1, 1) '; + } + + $return_array['textimage'] = $column_type === '[text]'; + + $sql .= 'NOT NULL'; + $sql_default .= 'NOT NULL'; + + $return_array['column_type_sql_default'] = $sql_default; + + break; + + case 'mysql_40': + case 'mysql_41': + $sql .= " {$column_type} "; + + // For hexadecimal values do not use single quotes + if (!is_null($column_data[1]) && substr($column_type, -4) !== 'text' && substr($column_type, -4) !== 'blob') + { + $sql .= (strpos($column_data[1], '0x') === 0) ? "DEFAULT {$column_data[1]} " : "DEFAULT '{$column_data[1]}' "; + } + $sql .= 'NOT NULL'; + + if (isset($column_data[2])) + { + if ($column_data[2] == 'auto_increment') + { + $sql .= ' auto_increment'; + } + else if ($this->sql_layer === 'mysql_41' && $column_data[2] == 'true_sort') + { + $sql .= ' COLLATE utf8_unicode_ci'; + } + } + + break; + + case 'oracle': + $sql .= " {$column_type} "; + $sql .= (!is_null($column_data[1])) ? "DEFAULT '{$column_data[1]}' " : ''; + + // In Oracle empty strings ('') are treated as NULL. + // Therefore in oracle we allow NULL's for all DEFAULT '' entries + // Oracle does not like setting NOT NULL on a column that is already NOT NULL (this happens only on number fields) + if (!preg_match('/number/i', $column_type)) + { + $sql .= ($column_data[1] === '') ? '' : 'NOT NULL'; + } + + $return_array['auto_increment'] = false; + if (isset($column_data[2]) && $column_data[2] == 'auto_increment') + { + $return_array['auto_increment'] = true; + } + + break; + + case 'postgres': + $return_array['column_type'] = $column_type; + + $sql .= " {$column_type} "; + + $return_array['auto_increment'] = false; + if (isset($column_data[2]) && $column_data[2] == 'auto_increment') + { + $default_val = "nextval('{$table_name}_seq')"; + $return_array['auto_increment'] = true; + } + else if (!is_null($column_data[1])) + { + $default_val = "'" . $column_data[1] . "'"; + $return_array['null'] = 'NOT NULL'; + $sql .= 'NOT NULL '; + } + + $return_array['default'] = $default_val; + + $sql .= "DEFAULT {$default_val}"; + + // Unsigned? Then add a CHECK contraint + if (in_array($orig_column_type, $this->unsigned_types)) + { + $return_array['constraint'] = "CHECK ({$column_name} >= 0)"; + $sql .= " CHECK ({$column_name} >= 0)"; + } + + break; + + case 'sqlite': + $return_array['primary_key_set'] = false; + if (isset($column_data[2]) && $column_data[2] == 'auto_increment') + { + $sql .= ' INTEGER PRIMARY KEY'; + $return_array['primary_key_set'] = true; + } + else + { + $sql .= ' ' . $column_type; + } + + $sql .= ' NOT NULL '; + $sql .= (!is_null($column_data[1])) ? "DEFAULT '{$column_data[1]}'" : ''; + + break; + } + + $return_array['column_type_sql'] = $sql; + + return $return_array; + } + + /** + * Add new column + */ + function sql_column_add($table_name, $column_name, $column_data, $inline = false) + { + $column_data = $this->sql_prepare_column_data($table_name, $column_name, $column_data); + $statements = array(); + + switch ($this->sql_layer) + { + case 'firebird': + // Does not support AFTER statement, only POSITION (and there you need the column position) + $statements[] = 'ALTER TABLE ' . $table_name . ' ADD "' . strtoupper($column_name) . '" ' . $column_data['column_type_sql']; + break; + + case 'mssql': + case 'mssqlnative': + // Does not support AFTER, only through temporary table + $statements[] = 'ALTER TABLE [' . $table_name . '] ADD [' . $column_name . '] ' . $column_data['column_type_sql_default']; + break; + + case 'mysql_40': + case 'mysql_41': + $after = (!empty($column_data['after'])) ? ' AFTER ' . $column_data['after'] : ''; + $statements[] = 'ALTER TABLE `' . $table_name . '` ADD COLUMN `' . $column_name . '` ' . $column_data['column_type_sql'] . $after; + break; + + case 'oracle': + // Does not support AFTER, only through temporary table + $statements[] = 'ALTER TABLE ' . $table_name . ' ADD ' . $column_name . ' ' . $column_data['column_type_sql']; + break; + + case 'postgres': + // Does not support AFTER, only through temporary table + if (version_compare($this->db->sql_server_info(true), '8.0', '>=')) + { + $statements[] = 'ALTER TABLE ' . $table_name . ' ADD COLUMN "' . $column_name . '" ' . $column_data['column_type_sql']; + } + else + { + // old versions cannot add columns with default and null information + $statements[] = 'ALTER TABLE ' . $table_name . ' ADD COLUMN "' . $column_name . '" ' . $column_data['column_type'] . ' ' . $column_data['constraint']; + + if (isset($column_data['null'])) + { + if ($column_data['null'] == 'NOT NULL') + { + $statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN ' . $column_name . ' SET NOT NULL'; + } + } + + if (isset($column_data['default'])) + { + $statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN ' . $column_name . ' SET DEFAULT ' . $column_data['default']; + } + } + + break; + + case 'sqlite': + + if ($inline && $this->return_statements) + { + return $column_name . ' ' . $column_data['column_type_sql']; + } + + if (version_compare(sqlite_libversion(), '3.0') == -1) + { + $sql = "SELECT sql + FROM sqlite_master + WHERE type = 'table' + AND name = '{$table_name}' + ORDER BY type DESC, name;"; + $result = $this->db->sql_query($sql); + + if (!$result) + { + break; + } + + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + $statements[] = 'begin'; + + // Create a backup table and populate it, destroy the existing one + $statements[] = preg_replace('#CREATE\s+TABLE\s+"?' . $table_name . '"?#i', 'CREATE TEMPORARY TABLE ' . $table_name . '_temp', $row['sql']); + $statements[] = 'INSERT INTO ' . $table_name . '_temp SELECT * FROM ' . $table_name; + $statements[] = 'DROP TABLE ' . $table_name; + + preg_match('#\((.*)\)#s', $row['sql'], $matches); + + $new_table_cols = trim($matches[1]); + $old_table_cols = preg_split('/,(?![\s\w]+\))/m', $new_table_cols); + $column_list = array(); + + foreach ($old_table_cols as $declaration) + { + $entities = preg_split('#\s+#', trim($declaration)); + if ($entities[0] == 'PRIMARY') + { + continue; + } + $column_list[] = $entities[0]; + } + + $columns = implode(',', $column_list); + + $new_table_cols = $column_name . ' ' . $column_data['column_type_sql'] . ',' . $new_table_cols; + + // create a new table and fill it up. destroy the temp one + $statements[] = 'CREATE TABLE ' . $table_name . ' (' . $new_table_cols . ');'; + $statements[] = 'INSERT INTO ' . $table_name . ' (' . $columns . ') SELECT ' . $columns . ' FROM ' . $table_name . '_temp;'; + $statements[] = 'DROP TABLE ' . $table_name . '_temp'; + + $statements[] = 'commit'; + } + else + { + $statements[] = 'ALTER TABLE ' . $table_name . ' ADD ' . $column_name . ' [' . $column_data['column_type_sql'] . ']'; + } + break; + } + + return $this->_sql_run_sql($statements); + } + + /** + * Drop column + */ + function sql_column_remove($table_name, $column_name, $inline = false) + { + $statements = array(); + + switch ($this->sql_layer) + { + case 'firebird': + $statements[] = 'ALTER TABLE ' . $table_name . ' DROP "' . strtoupper($column_name) . '"'; + break; + + case 'mssql': + case 'mssqlnative': + // remove default cosntraints first + // http://msdn.microsoft.com/en-us/library/aa175912%28v=sql.80%29.aspx + $statements[] = "DECLARE @drop_default_name VARCHAR(100), @cmd VARCHAR(1000) + SET @drop_default_name = + (SELECT so.name FROM sysobjects so + JOIN sysconstraints sc ON so.id = sc.constid + WHERE object_name(so.parent_obj) = '{$table_name}' + AND so.xtype = 'D' + AND sc.colid = (SELECT colid FROM syscolumns + WHERE id = object_id('{$table_name}') + AND name = '{$column_name}')) + IF @drop_default_name <> '' + BEGIN + SET @cmd = 'ALTER TABLE [{$table_name}] DROP CONSTRAINT [' + @drop_default_name + ']' + EXEC(@cmd) + END"; + $statements[] = 'ALTER TABLE [' . $table_name . '] DROP COLUMN [' . $column_name . ']'; + break; + + case 'mysql_40': + case 'mysql_41': + $statements[] = 'ALTER TABLE `' . $table_name . '` DROP COLUMN `' . $column_name . '`'; + break; + + case 'oracle': + $statements[] = 'ALTER TABLE ' . $table_name . ' DROP COLUMN ' . $column_name; + break; + + case 'postgres': + $statements[] = 'ALTER TABLE ' . $table_name . ' DROP COLUMN "' . $column_name . '"'; + break; + + case 'sqlite': + + if ($inline && $this->return_statements) + { + return $column_name; + } + + if (version_compare(sqlite_libversion(), '3.0') == -1) + { + $sql = "SELECT sql + FROM sqlite_master + WHERE type = 'table' + AND name = '{$table_name}' + ORDER BY type DESC, name;"; + $result = $this->db->sql_query($sql); + + if (!$result) + { + break; + } + + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + $statements[] = 'begin'; + + // Create a backup table and populate it, destroy the existing one + $statements[] = preg_replace('#CREATE\s+TABLE\s+"?' . $table_name . '"?#i', 'CREATE TEMPORARY TABLE ' . $table_name . '_temp', $row['sql']); + $statements[] = 'INSERT INTO ' . $table_name . '_temp SELECT * FROM ' . $table_name; + $statements[] = 'DROP TABLE ' . $table_name; + + preg_match('#\((.*)\)#s', $row['sql'], $matches); + + $new_table_cols = trim($matches[1]); + $old_table_cols = preg_split('/,(?![\s\w]+\))/m', $new_table_cols); + $column_list = array(); + + foreach ($old_table_cols as $declaration) + { + $entities = preg_split('#\s+#', trim($declaration)); + if ($entities[0] == 'PRIMARY' || $entities[0] === $column_name) + { + continue; + } + $column_list[] = $entities[0]; + } + + $columns = implode(',', $column_list); + + $new_table_cols = preg_replace('/' . $column_name . '[^,]+(?:,|$)/m', '', $new_table_cols); + + // create a new table and fill it up. destroy the temp one + $statements[] = 'CREATE TABLE ' . $table_name . ' (' . $new_table_cols . ');'; + $statements[] = 'INSERT INTO ' . $table_name . ' (' . $columns . ') SELECT ' . $columns . ' FROM ' . $table_name . '_temp;'; + $statements[] = 'DROP TABLE ' . $table_name . '_temp'; + + $statements[] = 'commit'; + } + else + { + $statements[] = 'ALTER TABLE ' . $table_name . ' DROP COLUMN ' . $column_name; + } + break; + } + + return $this->_sql_run_sql($statements); + } + + /** + * Drop Index + */ + function sql_index_drop($table_name, $index_name) + { + $statements = array(); + + switch ($this->sql_layer) + { + case 'mssql': + case 'mssqlnative': + $statements[] = 'DROP INDEX ' . $table_name . '.' . $index_name; + break; + + case 'mysql_40': + case 'mysql_41': + $statements[] = 'DROP INDEX ' . $index_name . ' ON ' . $table_name; + break; + + case 'firebird': + case 'oracle': + case 'postgres': + case 'sqlite': + $statements[] = 'DROP INDEX ' . $table_name . '_' . $index_name; + break; + } + + return $this->_sql_run_sql($statements); + } + + /** + * Drop Table + */ + function sql_table_drop($table_name) + { + $statements = array(); + + if (!$this->sql_table_exists($table_name)) + { + return $this->_sql_run_sql($statements); + } + + // the most basic operation, get rid of the table + $statements[] = 'DROP TABLE ' . $table_name; + + switch ($this->sql_layer) + { + case 'firebird': + $sql = 'SELECT RDB$GENERATOR_NAME as gen + FROM RDB$GENERATORS + WHERE RDB$SYSTEM_FLAG = 0 + AND RDB$GENERATOR_NAME = \'' . strtoupper($table_name) . "_GEN'"; + $result = $this->db->sql_query($sql); + + // does a generator exist? + if ($row = $this->db->sql_fetchrow($result)) + { + $statements[] = "DROP GENERATOR {$row['gen']};"; + } + $this->db->sql_freeresult($result); + break; + + case 'oracle': + $sql = 'SELECT A.REFERENCED_NAME + FROM USER_DEPENDENCIES A, USER_TRIGGERS B + WHERE A.REFERENCED_TYPE = \'SEQUENCE\' + AND A.NAME = B.TRIGGER_NAME + AND B.TABLE_NAME = \'' . strtoupper($table_name) . "'"; + $result = $this->db->sql_query($sql); + + // any sequences ref'd to this table's triggers? + while ($row = $this->db->sql_fetchrow($result)) + { + $statements[] = "DROP SEQUENCE {$row['referenced_name']}"; + } + $this->db->sql_freeresult($result); + break; + + case 'postgres': + // PGSQL does not "tightly" bind sequences and tables, we must guess... + $sql = "SELECT relname + FROM pg_class + WHERE relkind = 'S' + AND relname = '{$table_name}_seq'"; + $result = $this->db->sql_query($sql); + + // We don't even care about storing the results. We already know the answer if we get rows back. + if ($this->db->sql_fetchrow($result)) + { + $statements[] = "DROP SEQUENCE {$table_name}_seq;\n"; + } + $this->db->sql_freeresult($result); + break; + } + + return $this->_sql_run_sql($statements); + } + + /** + * Add primary key + */ + function sql_create_primary_key($table_name, $column, $inline = false) + { + $statements = array(); + + switch ($this->sql_layer) + { + case 'firebird': + case 'postgres': + case 'mysql_40': + case 'mysql_41': + $statements[] = 'ALTER TABLE ' . $table_name . ' ADD PRIMARY KEY (' . implode(', ', $column) . ')'; + break; + + case 'mssql': + case 'mssqlnative': + $sql = "ALTER TABLE [{$table_name}] WITH NOCHECK ADD "; + $sql .= "CONSTRAINT [PK_{$table_name}] PRIMARY KEY CLUSTERED ("; + $sql .= '[' . implode("],\n\t\t[", $column) . ']'; + $sql .= ') ON [PRIMARY]'; + + $statements[] = $sql; + break; + + case 'oracle': + $statements[] = 'ALTER TABLE ' . $table_name . 'add CONSTRAINT pk_' . $table_name . ' PRIMARY KEY (' . implode(', ', $column) . ')'; + break; + + case 'sqlite': + + if ($inline && $this->return_statements) + { + return $column; + } + + $sql = "SELECT sql + FROM sqlite_master + WHERE type = 'table' + AND name = '{$table_name}' + ORDER BY type DESC, name;"; + $result = $this->db->sql_query($sql); + + if (!$result) + { + break; + } + + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + $statements[] = 'begin'; + + // Create a backup table and populate it, destroy the existing one + $statements[] = preg_replace('#CREATE\s+TABLE\s+"?' . $table_name . '"?#i', 'CREATE TEMPORARY TABLE ' . $table_name . '_temp', $row['sql']); + $statements[] = 'INSERT INTO ' . $table_name . '_temp SELECT * FROM ' . $table_name; + $statements[] = 'DROP TABLE ' . $table_name; + + preg_match('#\((.*)\)#s', $row['sql'], $matches); + + $new_table_cols = trim($matches[1]); + $old_table_cols = preg_split('/,(?![\s\w]+\))/m', $new_table_cols); + $column_list = array(); + + foreach ($old_table_cols as $declaration) + { + $entities = preg_split('#\s+#', trim($declaration)); + if ($entities[0] == 'PRIMARY') + { + continue; + } + $column_list[] = $entities[0]; + } + + $columns = implode(',', $column_list); + + // create a new table and fill it up. destroy the temp one + $statements[] = 'CREATE TABLE ' . $table_name . ' (' . $new_table_cols . ', PRIMARY KEY (' . implode(', ', $column) . '));'; + $statements[] = 'INSERT INTO ' . $table_name . ' (' . $columns . ') SELECT ' . $columns . ' FROM ' . $table_name . '_temp;'; + $statements[] = 'DROP TABLE ' . $table_name . '_temp'; + + $statements[] = 'commit'; + break; + } + + return $this->_sql_run_sql($statements); + } + + /** + * Add unique index + */ + function sql_create_unique_index($table_name, $index_name, $column) + { + $statements = array(); + + $table_prefix = substr(CONFIG_TABLE, 0, -6); // strlen(config) + if (strlen($table_name . $index_name) - strlen($table_prefix) > 24) + { + $max_length = strlen($table_prefix) + 24; + trigger_error("Index name '{$table_name}_$index_name' on table '$table_name' is too long. The maximum is $max_length characters.", E_USER_ERROR); + } + + switch ($this->sql_layer) + { + case 'firebird': + case 'postgres': + case 'oracle': + case 'sqlite': + $statements[] = 'CREATE UNIQUE INDEX ' . $table_name . '_' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ')'; + break; + + case 'mysql_40': + case 'mysql_41': + $statements[] = 'ALTER TABLE ' . $table_name . ' ADD UNIQUE INDEX ' . $index_name . '(' . implode(', ', $column) . ')'; + break; + + case 'mssql': + case 'mssqlnative': + $statements[] = 'CREATE UNIQUE INDEX ' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ') ON [PRIMARY]'; + break; + } + + return $this->_sql_run_sql($statements); + } + + /** + * Add index + */ + function sql_create_index($table_name, $index_name, $column) + { + $statements = array(); + + $table_prefix = substr(CONFIG_TABLE, 0, -6); // strlen(config) + if (strlen($table_name . $index_name) - strlen($table_prefix) > 24) + { + $max_length = strlen($table_prefix) + 24; + trigger_error("Index name '{$table_name}_$index_name' on table '$table_name' is too long. The maximum is $max_length characters.", E_USER_ERROR); + } + + // remove index length unless MySQL4 + if ('mysql_40' != $this->sql_layer) + { + $column = preg_replace('#:.*$#', '', $column); + } + + switch ($this->sql_layer) + { + case 'firebird': + case 'postgres': + case 'oracle': + case 'sqlite': + $statements[] = 'CREATE INDEX ' . $table_name . '_' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ')'; + break; + + case 'mysql_40': + // add index size to definition as required by MySQL4 + foreach ($column as $i => $col) + { + if (false !== strpos($col, ':')) + { + list($col, $index_size) = explode(':', $col); + $column[$i] = "$col($index_size)"; + } + } + // no break + case 'mysql_41': + $statements[] = 'ALTER TABLE ' . $table_name . ' ADD INDEX ' . $index_name . '(' . implode(', ', $column) . ')'; + break; + + case 'mssql': + case 'mssqlnative': + $statements[] = 'CREATE INDEX ' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ') ON [PRIMARY]'; + break; + } + + return $this->_sql_run_sql($statements); + } + + /** + * List all of the indices that belong to a table, + * does not count: + * * UNIQUE indices + * * PRIMARY keys + */ + function sql_list_index($table_name) + { + $index_array = array(); + + if ($this->sql_layer == 'mssql' || $this->sql_layer == 'mssqlnative') + { + $sql = "EXEC sp_statistics '$table_name'"; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + if ($row['TYPE'] == 3) + { + $index_array[] = $row['INDEX_NAME']; + } + } + $this->db->sql_freeresult($result); + } + else + { + switch ($this->sql_layer) + { + case 'firebird': + $sql = "SELECT LOWER(RDB\$INDEX_NAME) as index_name + FROM RDB\$INDICES + WHERE RDB\$RELATION_NAME = '" . strtoupper($table_name) . "' + AND RDB\$UNIQUE_FLAG IS NULL + AND RDB\$FOREIGN_KEY IS NULL"; + $col = 'index_name'; + break; + + case 'postgres': + $sql = "SELECT ic.relname as index_name + FROM pg_class bc, pg_class ic, pg_index i + WHERE (bc.oid = i.indrelid) + AND (ic.oid = i.indexrelid) + AND (bc.relname = '" . $table_name . "') + AND (i.indisunique != 't') + AND (i.indisprimary != 't')"; + $col = 'index_name'; + break; + + case 'mysql_40': + case 'mysql_41': + $sql = 'SHOW KEYS + FROM ' . $table_name; + $col = 'Key_name'; + break; + + case 'oracle': + $sql = "SELECT index_name + FROM user_indexes + WHERE table_name = '" . strtoupper($table_name) . "' + AND generated = 'N' + AND uniqueness = 'NONUNIQUE'"; + $col = 'index_name'; + break; + + case 'sqlite': + $sql = "PRAGMA index_info('" . $table_name . "');"; + $col = 'name'; + break; + } + + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + if (($this->sql_layer == 'mysql_40' || $this->sql_layer == 'mysql_41') && !$row['Non_unique']) + { + continue; + } + + switch ($this->sql_layer) + { + case 'firebird': + case 'oracle': + case 'postgres': + case 'sqlite': + $row[$col] = substr($row[$col], strlen($table_name) + 1); + break; + } + + $index_array[] = $row[$col]; + } + $this->db->sql_freeresult($result); + } + + return array_map('strtolower', $index_array); + } + + /** + * Change column type (not name!) + */ + function sql_column_change($table_name, $column_name, $column_data, $inline = false) + { + $column_data = $this->sql_prepare_column_data($table_name, $column_name, $column_data); + $statements = array(); + + switch ($this->sql_layer) + { + case 'firebird': + // Change type... + if (!empty($column_data['column_type_sql_default'])) + { + $statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN "' . strtoupper($column_name) . '" TYPE ' . ' ' . $column_data['column_type_sql_type']; + $statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN "' . strtoupper($column_name) . '" SET DEFAULT ' . ' ' . $column_data['column_type_sql_default']; + } + else + { + // TODO: try to change pkey without removing trigger, generator or constraints. ATM this query may fail. + $statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN "' . strtoupper($column_name) . '" TYPE ' . ' ' . $column_data['column_type_sql_type']; + } + break; + + case 'mssql': + case 'mssqlnative': + $statements[] = 'ALTER TABLE [' . $table_name . '] ALTER COLUMN [' . $column_name . '] ' . $column_data['column_type_sql']; + + if (!empty($column_data['default'])) + { + // Using TRANSACT-SQL for this statement because we do not want to have colliding data if statements are executed at a later stage + $statements[] = "DECLARE @drop_default_name VARCHAR(100), @cmd VARCHAR(1000) + SET @drop_default_name = + (SELECT so.name FROM sysobjects so + JOIN sysconstraints sc ON so.id = sc.constid + WHERE object_name(so.parent_obj) = '{$table_name}' + AND so.xtype = 'D' + AND sc.colid = (SELECT colid FROM syscolumns + WHERE id = object_id('{$table_name}') + AND name = '{$column_name}')) + IF @drop_default_name <> '' + BEGIN + SET @cmd = 'ALTER TABLE [{$table_name}] DROP CONSTRAINT [' + @drop_default_name + ']' + EXEC(@cmd) + END + SET @cmd = 'ALTER TABLE [{$table_name}] ADD CONSTRAINT [DF_{$table_name}_{$column_name}_1] {$column_data['default']} FOR [{$column_name}]' + EXEC(@cmd)"; + } + break; + + case 'mysql_40': + case 'mysql_41': + $statements[] = 'ALTER TABLE `' . $table_name . '` CHANGE `' . $column_name . '` `' . $column_name . '` ' . $column_data['column_type_sql']; + break; + + case 'oracle': + $statements[] = 'ALTER TABLE ' . $table_name . ' MODIFY ' . $column_name . ' ' . $column_data['column_type_sql']; + break; + + case 'postgres': + $sql = 'ALTER TABLE ' . $table_name . ' '; + + $sql_array = array(); + $sql_array[] = 'ALTER COLUMN ' . $column_name . ' TYPE ' . $column_data['column_type']; + + if (isset($column_data['null'])) + { + if ($column_data['null'] == 'NOT NULL') + { + $sql_array[] = 'ALTER COLUMN ' . $column_name . ' SET NOT NULL'; + } + else if ($column_data['null'] == 'NULL') + { + $sql_array[] = 'ALTER COLUMN ' . $column_name . ' DROP NOT NULL'; + } + } + + if (isset($column_data['default'])) + { + $sql_array[] = 'ALTER COLUMN ' . $column_name . ' SET DEFAULT ' . $column_data['default']; + } + + // we don't want to double up on constraints if we change different number data types + if (isset($column_data['constraint'])) + { + $constraint_sql = "SELECT consrc as constraint_data + FROM pg_constraint, pg_class bc + WHERE conrelid = bc.oid + AND bc.relname = '{$table_name}' + AND NOT EXISTS ( + SELECT * + FROM pg_constraint as c, pg_inherits as i + WHERE i.inhrelid = pg_constraint.conrelid + AND c.conname = pg_constraint.conname + AND c.consrc = pg_constraint.consrc + AND c.conrelid = i.inhparent + )"; + + $constraint_exists = false; + + $result = $this->db->sql_query($constraint_sql); + while ($row = $this->db->sql_fetchrow($result)) + { + if (trim($row['constraint_data']) == trim($column_data['constraint'])) + { + $constraint_exists = true; + break; + } + } + $this->db->sql_freeresult($result); + + if (!$constraint_exists) + { + $sql_array[] = 'ADD ' . $column_data['constraint']; + } + } + + $sql .= implode(', ', $sql_array); + + $statements[] = $sql; + break; + + case 'sqlite': + + if ($inline && $this->return_statements) + { + return $column_name . ' ' . $column_data['column_type_sql']; + } + + $sql = "SELECT sql + FROM sqlite_master + WHERE type = 'table' + AND name = '{$table_name}' + ORDER BY type DESC, name;"; + $result = $this->db->sql_query($sql); + + if (!$result) + { + break; + } + + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + $statements[] = 'begin'; + + // Create a temp table and populate it, destroy the existing one + $statements[] = preg_replace('#CREATE\s+TABLE\s+"?' . $table_name . '"?#i', 'CREATE TEMPORARY TABLE ' . $table_name . '_temp', $row['sql']); + $statements[] = 'INSERT INTO ' . $table_name . '_temp SELECT * FROM ' . $table_name; + $statements[] = 'DROP TABLE ' . $table_name; + + preg_match('#\((.*)\)#s', $row['sql'], $matches); + + $new_table_cols = trim($matches[1]); + $old_table_cols = preg_split('/,(?![\s\w]+\))/m', $new_table_cols); + $column_list = array(); + + foreach ($old_table_cols as $key => $declaration) + { + $entities = preg_split('#\s+#', trim($declaration)); + $column_list[] = $entities[0]; + if ($entities[0] == $column_name) + { + $old_table_cols[$key] = $column_name . ' ' . $column_data['column_type_sql']; + } + } + + $columns = implode(',', $column_list); + + // create a new table and fill it up. destroy the temp one + $statements[] = 'CREATE TABLE ' . $table_name . ' (' . implode(',', $old_table_cols) . ');'; + $statements[] = 'INSERT INTO ' . $table_name . ' (' . $columns . ') SELECT ' . $columns . ' FROM ' . $table_name . '_temp;'; + $statements[] = 'DROP TABLE ' . $table_name . '_temp'; + + $statements[] = 'commit'; + + break; + } + + return $this->_sql_run_sql($statements); + } +} -- cgit v1.2.1 From 405b5e54f6e97f70417af38c27f8fa2fb4062c3e Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Sun, 14 Jul 2013 10:49:23 -0400 Subject: [ticket/11702] Fix forum_posts left over for link-click counts in viewforum.php PHPBB3-11702 --- phpBB/viewforum.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB') diff --git a/phpBB/viewforum.php b/phpBB/viewforum.php index 06af674ee6..5a59e021b3 100644 --- a/phpBB/viewforum.php +++ b/phpBB/viewforum.php @@ -105,7 +105,7 @@ if ($forum_data['forum_type'] == FORUM_LINK && $forum_data['forum_link']) if ($forum_data['forum_flags'] & FORUM_FLAG_LINK_TRACK) { $sql = 'UPDATE ' . FORUMS_TABLE . ' - SET forum_posts = forum_posts + 1 + SET forum_posts_approved = forum_posts_approved + 1 WHERE forum_id = ' . $forum_id; $db->sql_query($sql); } -- cgit v1.2.1 From e91b465de1c4cf767bba72c9eebe945b898c3fda Mon Sep 17 00:00:00 2001 From: Victor Nagy Date: Sun, 14 Jul 2013 10:57:19 -0400 Subject: [ticket/11697] author_search() used incorrect parameter The author_search() function in fulltext_mysql.php had an argument $m_approve_fid_ary parameter but it was changed in the Docblock and code to $post_visibility. This change renames that argument according to the docblock, the code and to mirror the other classes. PHPBB3-11697 --- phpBB/includes/search/fulltext_mysql.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB') diff --git a/phpBB/includes/search/fulltext_mysql.php b/phpBB/includes/search/fulltext_mysql.php index 7dc4da8ffe..a1e1b089b9 100644 --- a/phpBB/includes/search/fulltext_mysql.php +++ b/phpBB/includes/search/fulltext_mysql.php @@ -542,7 +542,7 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base * @param int $per_page number of ids each page is supposed to contain * @return boolean|int total number of results */ - public function author_search($type, $firstpost_only, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $m_approve_fid_ary, $topic_id, $author_ary, $author_name, &$id_ary, &$start, $per_page) + public function author_search($type, $firstpost_only, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $post_visibility, $topic_id, $author_ary, $author_name, &$id_ary, &$start, $per_page) { // No author? No posts if (!sizeof($author_ary)) -- cgit v1.2.1 From 573987d2d2defe3425c083b093bb5a5d3ec2db2a Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Fri, 28 Jun 2013 10:52:13 +0200 Subject: [ticket/11582] Add new service for permissions Replace calls to the language-array type with a call to get_types() PHPBB3-11582 --- phpBB/config/services.yml | 5 + phpBB/includes/acp/acp_permissions.php | 9 +- phpBB/includes/permissions.php | 263 ++++++++++++++++++++++++++++ phpBB/language/en/acp/permissions_phpbb.php | 16 +- 4 files changed, 280 insertions(+), 13 deletions(-) create mode 100644 phpBB/includes/permissions.php (limited to 'phpBB') diff --git a/phpBB/config/services.yml b/phpBB/config/services.yml index c1579cfb57..9337fb0a5b 100644 --- a/phpBB/config/services.yml +++ b/phpBB/config/services.yml @@ -8,6 +8,11 @@ imports: - { resource: auth_providers.yml } services: + acl.permissions: + class: phpbb_permissions + arguments: + - @dispatcher + auth: class: phpbb_auth diff --git a/phpBB/includes/acp/acp_permissions.php b/phpBB/includes/acp/acp_permissions.php index a64765f4f5..9c5395c5b2 100644 --- a/phpBB/includes/acp/acp_permissions.php +++ b/phpBB/includes/acp/acp_permissions.php @@ -587,7 +587,10 @@ class acp_permissions */ function build_permission_dropdown($options, $default_option, $permission_scope) { - global $user, $auth; + global $user, $auth, $phpbb_container; + + $permissions = $phpbb_container->get('acl.permissions'); + $permission_types = $permissions->get_types(); $s_dropdown_options = ''; foreach ($options as $setting) @@ -598,8 +601,8 @@ class acp_permissions } $selected = ($setting == $default_option) ? ' selected="selected"' : ''; - $l_setting = (isset($user->lang['permission_type'][$permission_scope][$setting])) ? $user->lang['permission_type'][$permission_scope][$setting] : $user->lang['permission_type'][$setting]; - $s_dropdown_options .= ''; + $l_setting = (isset($permission_types[$permission_scope][$setting])) ? $permission_types[$permission_scope][$setting] : $permission_types[$setting]; + $s_dropdown_options .= ''; } return $s_dropdown_options; diff --git a/phpBB/includes/permissions.php b/phpBB/includes/permissions.php new file mode 100644 index 0000000000..d5389344f7 --- /dev/null +++ b/phpBB/includes/permissions.php @@ -0,0 +1,263 @@ +dispatcher = $phpbb_dispatcher; + } + + public function get_categories() + { + $categories = $this->categories; + + /** + * Allows to specify additional permission categories + * + * @event core.permissions_get_categories + * @var array categories Array with permission categories (pm, post, settings, misc, etc.) + * @since 3.1-A1 + */ + $vars = array('categories'); + extract($this->dispatcher->trigger_event('core.permissions_get_categories', $vars)); + + return $categories; + } + + public function get_types() + { + $types = $this->types; + + /** + * Allows to specify additional permission types + * + * @event core.permissions_get_types + * @var array types Array with permission types (a_, u_, m_, etc.) + * @since 3.1-A1 + */ + $vars = array('types'); + extract($this->dispatcher->trigger_event('core.permissions_get_types', $vars)); + + return $types; + } + + public function get_permissions() + { + $permissions = $this->permissions; + + /** + * Allows to specify additional permissions + * + * @event core.permissions_get_types + * @var array permissions Array with permissions. Each Permission has the following layout: + * 'acl_' => array( + * 'lang' => 'Language Key with a Short description', // Optional, if not set, + * // the permissions identifier 'acl_' is used with + * // all uppercase. + * 'cat' => 'Identifier of the category, the permission should be displayed in', + * ), + * Example: + * 'acl_u_viewprofile' => array( + * 'lang' => 'ACL_U_VIEWPROFILE', + * 'cat' => 'profile', + * ), + * + * @since 3.1-A1 + */ + $vars = array('permissions'); + extract($this->dispatcher->trigger_event('core.permissions_get_permissions', $vars)); + + return $permissions; + } + + protected $types = array( + 'u_' => 'ACL_TYPE_USER', + 'a_' => 'ACL_TYPE_ADMIN', + 'm_' => 'ACL_TYPE_MODERATOR', + 'f_' => 'ACL_TYPE_FORUM', + 'global' => array( + 'm_' => 'ACL_TYPE_GLOBAL_MODERATOR', + ), + ); + + protected $categories = array( + 'actions' => 'Actions', + 'content' => 'Content', + 'forums' => 'Forums', + 'misc' => 'Misc', + 'permissions' => 'Permissions', + 'pm' => 'Private messages', + 'polls' => 'Polls', + 'post' => 'Post', + 'post_actions' => 'Post actions', + 'posting' => 'Posting', + 'profile' => 'Profile', + 'settings' => 'Settings', + 'topic_actions' => 'Topic actions', + 'user_group' => 'Users & Groups', + ); + + protected $permissions = array( + // User Permissions + 'acl_u_viewprofile' => array('lang' => 'Can view profiles, memberlist and online list', 'cat' => 'profile'), + 'acl_u_chgname' => array('lang' => 'Can change username', 'cat' => 'profile'), + 'acl_u_chgpasswd' => array('lang' => 'Can change password', 'cat' => 'profile'), + 'acl_u_chgemail' => array('lang' => 'Can change email address', 'cat' => 'profile'), + 'acl_u_chgavatar' => array('lang' => 'Can change avatar', 'cat' => 'profile'), + 'acl_u_chggrp' => array('lang' => 'Can change default usergroup', 'cat' => 'profile'), + 'acl_u_chgprofileinfo' => array('lang' => 'Can change profile field information', 'cat' => 'profile'), + + 'acl_u_attach' => array('lang' => 'Can attach files', 'cat' => 'post'), + 'acl_u_download' => array('lang' => 'Can download files', 'cat' => 'post'), + 'acl_u_savedrafts' => array('lang' => 'Can save drafts', 'cat' => 'post'), + 'acl_u_chgcensors' => array('lang' => 'Can disable word censors', 'cat' => 'post'), + 'acl_u_sig' => array('lang' => 'Can use signature', 'cat' => 'post'), + + 'acl_u_sendpm' => array('lang' => 'Can send private messages', 'cat' => 'pm'), + 'acl_u_masspm' => array('lang' => 'Can send messages to multiple users', 'cat' => 'pm'), + 'acl_u_masspm_group'=> array('lang' => 'Can send messages to groups', 'cat' => 'pm'), + 'acl_u_readpm' => array('lang' => 'Can read private messages', 'cat' => 'pm'), + 'acl_u_pm_edit' => array('lang' => 'Can edit own private messages', 'cat' => 'pm'), + 'acl_u_pm_delete' => array('lang' => 'Can remove private messages from own folder', 'cat' => 'pm'), + 'acl_u_pm_forward' => array('lang' => 'Can forward private messages', 'cat' => 'pm'), + 'acl_u_pm_emailpm' => array('lang' => 'Can email private messages', 'cat' => 'pm'), + 'acl_u_pm_printpm' => array('lang' => 'Can print private messages', 'cat' => 'pm'), + 'acl_u_pm_attach' => array('lang' => 'Can attach files in private messages', 'cat' => 'pm'), + 'acl_u_pm_download' => array('lang' => 'Can download files in private messages', 'cat' => 'pm'), + 'acl_u_pm_bbcode' => array('lang' => 'Can use BBCode in private messages', 'cat' => 'pm'), + 'acl_u_pm_smilies' => array('lang' => 'Can use smilies in private messages', 'cat' => 'pm'), + 'acl_u_pm_img' => array('lang' => 'Can use [img] BBCode tag in private messages', 'cat' => 'pm'), + 'acl_u_pm_flash' => array('lang' => 'Can use [flash] BBCode tag in private messages', 'cat' => 'pm'), + + 'acl_u_sendemail' => array('lang' => 'Can send emails', 'cat' => 'misc'), + 'acl_u_sendim' => array('lang' => 'Can send instant messages', 'cat' => 'misc'), + 'acl_u_ignoreflood' => array('lang' => 'Can ignore flood limit', 'cat' => 'misc'), + 'acl_u_hideonline' => array('lang' => 'Can hide online status', 'cat' => 'misc'), + 'acl_u_viewonline' => array('lang' => 'Can view hidden online users', 'cat' => 'misc'), + 'acl_u_search' => array('lang' => 'Can search board', 'cat' => 'misc'), + + // Forum Permissions + 'acl_f_list' => array('lang' => 'Can see forum', 'cat' => 'actions'), + 'acl_f_read' => array('lang' => 'Can read forum', 'cat' => 'actions'), + 'acl_f_search' => array('lang' => 'Can search the forum', 'cat' => 'actions'), + 'acl_f_subscribe' => array('lang' => 'Can subscribe forum', 'cat' => 'actions'), + 'acl_f_print' => array('lang' => 'Can print topics', 'cat' => 'actions'), + 'acl_f_email' => array('lang' => 'Can email topics', 'cat' => 'actions'), + 'acl_f_bump' => array('lang' => 'Can bump topics', 'cat' => 'actions'), + 'acl_f_user_lock' => array('lang' => 'Can lock own topics', 'cat' => 'actions'), + 'acl_f_download' => array('lang' => 'Can download files', 'cat' => 'actions'), + 'acl_f_report' => array('lang' => 'Can report posts', 'cat' => 'actions'), + + 'acl_f_post' => array('lang' => 'Can start new topics', 'cat' => 'post'), + 'acl_f_sticky' => array('lang' => 'Can post stickies', 'cat' => 'post'), + 'acl_f_announce' => array('lang' => 'Can post announcements', 'cat' => 'post'), + 'acl_f_reply' => array('lang' => 'Can reply to topics', 'cat' => 'post'), + 'acl_f_edit' => array('lang' => 'Can edit own posts', 'cat' => 'post'), + 'acl_f_delete' => array('lang' => 'Can delete own posts', 'cat' => 'post'), + 'acl_f_ignoreflood' => array('lang' => 'Can ignore flood limit', 'cat' => 'post'), + 'acl_f_postcount' => array('lang' => 'Increment post counter
Please note that this setting only affects new posts.', 'cat' => 'post'), + 'acl_f_noapprove' => array('lang' => 'Can post without approval', 'cat' => 'post'), + + 'acl_f_attach' => array('lang' => 'Can attach files', 'cat' => 'content'), + 'acl_f_icons' => array('lang' => 'Can use topic/post icons', 'cat' => 'content'), + 'acl_f_bbcode' => array('lang' => 'Can use BBCode', 'cat' => 'content'), + 'acl_f_flash' => array('lang' => 'Can use [flash] BBCode tag', 'cat' => 'content'), + 'acl_f_img' => array('lang' => 'Can use [img] BBCode tag', 'cat' => 'content'), + 'acl_f_sigs' => array('lang' => 'Can use signatures', 'cat' => 'content'), + 'acl_f_smilies' => array('lang' => 'Can use smilies', 'cat' => 'content'), + + 'acl_f_poll' => array('lang' => 'Can create polls', 'cat' => 'polls'), + 'acl_f_vote' => array('lang' => 'Can vote in polls', 'cat' => 'polls'), + 'acl_f_votechg' => array('lang' => 'Can change existing vote', 'cat' => 'polls'), + + // Moderator Permissions + 'acl_m_edit' => array('lang' => 'Can edit posts', 'cat' => 'post_actions'), + 'acl_m_delete' => array('lang' => 'Can delete posts', 'cat' => 'post_actions'), + 'acl_m_approve' => array('lang' => 'Can approve posts', 'cat' => 'post_actions'), + 'acl_m_report' => array('lang' => 'Can close and delete reports', 'cat' => 'post_actions'), + 'acl_m_chgposter' => array('lang' => 'Can change post author', 'cat' => 'post_actions'), + + 'acl_m_move' => array('lang' => 'Can move topics', 'cat' => 'topic_actions'), + 'acl_m_lock' => array('lang' => 'Can lock topics', 'cat' => 'topic_actions'), + 'acl_m_split' => array('lang' => 'Can split topics', 'cat' => 'topic_actions'), + 'acl_m_merge' => array('lang' => 'Can merge topics', 'cat' => 'topic_actions'), + + 'acl_m_info' => array('lang' => 'Can view post details', 'cat' => 'misc'), + 'acl_m_warn' => array('lang' => 'Can issue warnings
This setting is only assigned globally. It is not forum based.', 'cat' => 'misc'), // This moderator setting is only global (and not local) + 'acl_m_ban' => array('lang' => 'Can manage bans
This setting is only assigned globally. It is not forum based.', 'cat' => 'misc'), // This moderator setting is only global (and not local) + + // Admin Permissions + 'acl_a_board' => array('lang' => 'Can alter board settings/check for updates', 'cat' => 'settings'), + 'acl_a_server' => array('lang' => 'Can alter server/communication settings', 'cat' => 'settings'), + 'acl_a_jabber' => array('lang' => 'Can alter Jabber settings', 'cat' => 'settings'), + 'acl_a_phpinfo' => array('lang' => 'Can view php settings', 'cat' => 'settings'), + + 'acl_a_forum' => array('lang' => 'Can manage forums', 'cat' => 'forums'), + 'acl_a_forumadd' => array('lang' => 'Can add new forums', 'cat' => 'forums'), + 'acl_a_forumdel' => array('lang' => 'Can delete forums', 'cat' => 'forums'), + 'acl_a_prune' => array('lang' => 'Can prune forums', 'cat' => 'forums'), + + 'acl_a_icons' => array('lang' => 'Can alter topic/post icons and smilies', 'cat' => 'posting'), + 'acl_a_words' => array('lang' => 'Can alter word censors', 'cat' => 'posting'), + 'acl_a_bbcode' => array('lang' => 'Can define BBCode tags', 'cat' => 'posting'), + 'acl_a_attach' => array('lang' => 'Can alter attachment related settings', 'cat' => 'posting'), + + 'acl_a_user' => array('lang' => 'Can manage users
This also includes seeing the users browser agent within the viewonline list.', 'cat' => 'user_group'), + 'acl_a_userdel' => array('lang' => 'Can delete/prune users', 'cat' => 'user_group'), + 'acl_a_group' => array('lang' => 'Can manage groups', 'cat' => 'user_group'), + 'acl_a_groupadd' => array('lang' => 'Can add new groups', 'cat' => 'user_group'), + 'acl_a_groupdel' => array('lang' => 'Can delete groups', 'cat' => 'user_group'), + 'acl_a_ranks' => array('lang' => 'Can manage ranks', 'cat' => 'user_group'), + 'acl_a_profile' => array('lang' => 'Can manage custom profile fields', 'cat' => 'user_group'), + 'acl_a_names' => array('lang' => 'Can manage disallowed names', 'cat' => 'user_group'), + 'acl_a_ban' => array('lang' => 'Can manage bans', 'cat' => 'user_group'), + + 'acl_a_viewauth' => array('lang' => 'Can view permission masks', 'cat' => 'permissions'), + 'acl_a_authgroups' => array('lang' => 'Can alter permissions for individual groups', 'cat' => 'permissions'), + 'acl_a_authusers' => array('lang' => 'Can alter permissions for individual users', 'cat' => 'permissions'), + 'acl_a_fauth' => array('lang' => 'Can alter forum permission class', 'cat' => 'permissions'), + 'acl_a_mauth' => array('lang' => 'Can alter moderator permission class', 'cat' => 'permissions'), + 'acl_a_aauth' => array('lang' => 'Can alter admin permission class', 'cat' => 'permissions'), + 'acl_a_uauth' => array('lang' => 'Can alter user permission class', 'cat' => 'permissions'), + 'acl_a_roles' => array('lang' => 'Can manage roles', 'cat' => 'permissions'), + 'acl_a_switchperm' => array('lang' => 'Can use others permissions', 'cat' => 'permissions'), + + 'acl_a_styles' => array('lang' => 'Can manage styles', 'cat' => 'misc'), + 'acl_a_extensions' => array('lang' => 'Can manage extensions', 'cat' => 'misc'), + 'acl_a_viewlogs' => array('lang' => 'Can view logs', 'cat' => 'misc'), + 'acl_a_clearlogs' => array('lang' => 'Can clear logs', 'cat' => 'misc'), + 'acl_a_modules' => array('lang' => 'Can manage modules', 'cat' => 'misc'), + 'acl_a_language' => array('lang' => 'Can manage language packs', 'cat' => 'misc'), + 'acl_a_email' => array('lang' => 'Can send mass email', 'cat' => 'misc'), + 'acl_a_bots' => array('lang' => 'Can manage bots', 'cat' => 'misc'), + 'acl_a_reasons' => array('lang' => 'Can manage report/denial reasons', 'cat' => 'misc'), + 'acl_a_backup' => array('lang' => 'Can backup/restore database', 'cat' => 'misc'), + 'acl_a_search' => array('lang' => 'Can manage search backends and settings', 'cat' => 'misc'), + ); +} diff --git a/phpBB/language/en/acp/permissions_phpbb.php b/phpBB/language/en/acp/permissions_phpbb.php index 98679ad544..70312261a1 100644 --- a/phpBB/language/en/acp/permissions_phpbb.php +++ b/phpBB/language/en/acp/permissions_phpbb.php @@ -82,16 +82,12 @@ $lang = array_merge($lang, array( 'user_group' => 'Users & Groups', ), - // With defining 'global' here we are able to specify what is printed out if the permission is within the global scope. - 'permission_type' => array( - 'u_' => 'User permissions', - 'a_' => 'Admin permissions', - 'm_' => 'Moderator permissions', - 'f_' => 'Forum permissions', - 'global' => array( - 'm_' => 'Global moderator permissions', - ), - ), + + 'ACL_TYPE_USER' => 'User permissions', + 'ACL_TYPE_ADMIN' => 'Admin permissions', + 'ACL_TYPE_MODERATOR' => 'Moderator permissions', + 'ACL_TYPE_FORUM' => 'Forum permissions', + 'ACL_TYPE_GLOBAL_MODERATOR' => 'Global moderator permissions', )); // User Permissions -- cgit v1.2.1 From e8d2a2fd8861e5ef2473aa670808dc6098aa1241 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Fri, 28 Jun 2013 11:10:33 +0200 Subject: [ticket/11582] Use new class for categories PHPBB3-11582 --- phpBB/includes/acp/acp_permission_roles.php | 7 +++++-- phpBB/includes/acp/auth.php | 16 ++++++++++----- phpBB/includes/permissions.php | 28 +++++++++++++-------------- phpBB/language/en/acp/permissions_phpbb.php | 30 ++++++++++++++--------------- 4 files changed, 44 insertions(+), 37 deletions(-) (limited to 'phpBB') diff --git a/phpBB/includes/acp/acp_permission_roles.php b/phpBB/includes/acp/acp_permission_roles.php index e830479389..8a2798a90a 100644 --- a/phpBB/includes/acp/acp_permission_roles.php +++ b/phpBB/includes/acp/acp_permission_roles.php @@ -456,7 +456,10 @@ class acp_permission_roles */ function display_auth_options($auth_options) { - global $template, $user; + global $template, $user, $phpbb_container; + + $permissions = $phpbb_container->get('acl.permissions'); + $permission_categories = $permissions->get_categories(); $content_array = $categories = array(); $key_sort_array = array(0); @@ -473,7 +476,7 @@ class acp_permission_roles foreach ($content_array as $cat => $cat_array) { $template->assign_block_vars('auth', array( - 'CAT_NAME' => $user->lang['permission_cat'][$cat], + 'CAT_NAME' => $user->lang($permission_categories[$cat]), 'S_YES' => ($cat_array['S_YES'] && !$cat_array['S_NEVER'] && !$cat_array['S_NO']) ? true : false, 'S_NEVER' => ($cat_array['S_NEVER'] && !$cat_array['S_YES'] && !$cat_array['S_NO']) ? true : false, diff --git a/phpBB/includes/acp/auth.php b/phpBB/includes/acp/auth.php index 6b1da46a12..03cc0c1705 100644 --- a/phpBB/includes/acp/auth.php +++ b/phpBB/includes/acp/auth.php @@ -1100,7 +1100,10 @@ class auth_admin extends phpbb_auth */ function assign_cat_array(&$category_array, $tpl_cat, $tpl_mask, $ug_id, $forum_id, $show_trace = false, $s_view) { - global $template, $user, $phpbb_admin_path, $phpEx; + global $template, $user, $phpbb_admin_path, $phpEx, $phpbb_container; + + $permissions = $phpbb_container->get('acl.permissions'); + $permission_categories = $permissions->get_categories(); @reset($category_array); while (list($cat, $cat_array) = each($category_array)) @@ -1110,8 +1113,8 @@ class auth_admin extends phpbb_auth 'S_NEVER' => ($cat_array['S_NEVER'] && !$cat_array['S_YES'] && !$cat_array['S_NO']) ? true : false, 'S_NO' => ($cat_array['S_NO'] && !$cat_array['S_NEVER'] && !$cat_array['S_YES']) ? true : false, - 'CAT_NAME' => $user->lang['permission_cat'][$cat]) - ); + 'CAT_NAME' => $user->lang($permission_categories[$cat]), + )); /* Sort permissions by name (more naturaly and user friendly than sorting by a primary key) * Commented out due to it's memory consumption and time needed @@ -1176,7 +1179,10 @@ class auth_admin extends phpbb_auth */ function build_permission_array(&$permission_row, &$content_array, &$categories, $key_sort_array) { - global $user; + global $user, $phpbb_container; + + $permissions = $phpbb_container->get('acl.permissions'); + $permission_categories = $permissions->get_categories(); foreach ($key_sort_array as $forum_id) { @@ -1204,7 +1210,7 @@ class auth_admin extends phpbb_auth // Build our categories array if (!isset($categories[$cat])) { - $categories[$cat] = $user->lang['permission_cat'][$cat]; + $categories[$cat] = $user->lang($permission_categories[$cat]); } // Build our content array diff --git a/phpBB/includes/permissions.php b/phpBB/includes/permissions.php index d5389344f7..1e2cf9e4aa 100644 --- a/phpBB/includes/permissions.php +++ b/phpBB/includes/permissions.php @@ -107,20 +107,20 @@ class phpbb_permissions ); protected $categories = array( - 'actions' => 'Actions', - 'content' => 'Content', - 'forums' => 'Forums', - 'misc' => 'Misc', - 'permissions' => 'Permissions', - 'pm' => 'Private messages', - 'polls' => 'Polls', - 'post' => 'Post', - 'post_actions' => 'Post actions', - 'posting' => 'Posting', - 'profile' => 'Profile', - 'settings' => 'Settings', - 'topic_actions' => 'Topic actions', - 'user_group' => 'Users & Groups', + 'actions' => 'ACL_CAT_ACTIONS', + 'content' => 'ACL_CAT_CONTENT', + 'forums' => 'ACL_CAT_FORUMS', + 'misc' => 'ACL_CAT_MISC', + 'permissions' => 'ACL_CAT_PERMISSIONS', + 'pm' => 'ACL_CAT_PM', + 'polls' => 'ACL_CAT_POLLS', + 'post' => 'ACL_CAT_POST', + 'post_actions' => 'ACL_CAT_POST_ACTIONS', + 'posting' => 'ACL_CAT_POSTING', + 'profile' => 'ACL_CAT_PROFILE', + 'settings' => 'ACL_CAT_SETTINGS', + 'topic_actions' => 'ACL_CAT_TOPIC_ACTIONS', + 'user_group' => 'ACL_CAT_USER_GROUP', ); protected $permissions = array( diff --git a/phpBB/language/en/acp/permissions_phpbb.php b/phpBB/language/en/acp/permissions_phpbb.php index 70312261a1..0a669a4b8d 100644 --- a/phpBB/language/en/acp/permissions_phpbb.php +++ b/phpBB/language/en/acp/permissions_phpbb.php @@ -65,22 +65,20 @@ if (empty($lang) || !is_array($lang)) // Define categories and permission types $lang = array_merge($lang, array( - 'permission_cat' => array( - 'actions' => 'Actions', - 'content' => 'Content', - 'forums' => 'Forums', - 'misc' => 'Misc', - 'permissions' => 'Permissions', - 'pm' => 'Private messages', - 'polls' => 'Polls', - 'post' => 'Post', - 'post_actions' => 'Post actions', - 'posting' => 'Posting', - 'profile' => 'Profile', - 'settings' => 'Settings', - 'topic_actions' => 'Topic actions', - 'user_group' => 'Users & Groups', - ), + 'ACL_CAT_ACTIONS' => 'Actions', + 'ACL_CAT_CONTENT' => 'Content', + 'ACL_CAT_FORUMS' => 'Forums', + 'ACL_CAT_MISC' => 'Misc', + 'ACL_CAT_PERMISSIONS' => 'Permissions', + 'ACL_CAT_PM' => 'Private messages', + 'ACL_CAT_POLLS' => 'Polls', + 'ACL_CAT_POST' => 'Post', + 'ACL_CAT_POST_ACTIONS' => 'Post actions', + 'ACL_CAT_POSTING' => 'Posting', + 'ACL_CAT_PROFILE' => 'Profile', + 'ACL_CAT_SETTINGS' => 'Settings', + 'ACL_CAT_TOPIC_ACTIONS' => 'Topic actions', + 'ACL_CAT_USER_GROUP' => 'Users & Groups', 'ACL_TYPE_USER' => 'User permissions', -- cgit v1.2.1 From 137cec58950ad547a204a47b1e84e46fdc29139c Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Fri, 28 Jun 2013 11:16:52 +0200 Subject: [ticket/11582] Fix event dispatcher class name PHPBB3-11582 --- phpBB/includes/permissions.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'phpBB') diff --git a/phpBB/includes/permissions.php b/phpBB/includes/permissions.php index 1e2cf9e4aa..c338727fc0 100644 --- a/phpBB/includes/permissions.php +++ b/phpBB/includes/permissions.php @@ -19,16 +19,16 @@ class phpbb_permissions { /** * Event dispatcher object - * @var phpbb_dispatcher + * @var phpbb_event_dispatcher */ protected $dispatcher; /** * Constructor * - * @param phpbb_dispatcher $phpbb_dispatcher Event dispatcher + * @param phpbb_event_dispatcher $phpbb_dispatcher Event dispatcher * @return null */ - public function __construct($phpbb_dispatcher) + public function __construct(phpbb_event_dispatcher $phpbb_dispatcher) { $this->dispatcher = $phpbb_dispatcher; } -- cgit v1.2.1 From 7f9a1c811647c00b20cfa4f5029f6b569597cb4b Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Fri, 28 Jun 2013 11:27:38 +0200 Subject: [ticket/11582] Add event in constructor and add docs PHPBB3-11582 --- phpBB/config/services.yml | 1 + phpBB/includes/permissions.php | 103 ++++++++++++++++++++++++----------------- 2 files changed, 62 insertions(+), 42 deletions(-) (limited to 'phpBB') diff --git a/phpBB/config/services.yml b/phpBB/config/services.yml index 9337fb0a5b..4110f8c456 100644 --- a/phpBB/config/services.yml +++ b/phpBB/config/services.yml @@ -12,6 +12,7 @@ services: class: phpbb_permissions arguments: - @dispatcher + - @user auth: class: phpbb_auth diff --git a/phpBB/includes/permissions.php b/phpBB/includes/permissions.php index c338727fc0..ddeadc825b 100644 --- a/phpBB/includes/permissions.php +++ b/phpBB/includes/permissions.php @@ -22,59 +22,35 @@ class phpbb_permissions * @var phpbb_event_dispatcher */ protected $dispatcher; + + /** + * User object + * @var phpbb_user + */ + protected $user; + /** * Constructor * * @param phpbb_event_dispatcher $phpbb_dispatcher Event dispatcher + * @param phpbb_user $user User Object * @return null */ - public function __construct(phpbb_event_dispatcher $phpbb_dispatcher) + public function __construct(phpbb_event_dispatcher $phpbb_dispatcher, phpbb_user $user) { $this->dispatcher = $phpbb_dispatcher; - } + $this->user = $user; - public function get_categories() - { $categories = $this->categories; - - /** - * Allows to specify additional permission categories - * - * @event core.permissions_get_categories - * @var array categories Array with permission categories (pm, post, settings, misc, etc.) - * @since 3.1-A1 - */ - $vars = array('categories'); - extract($this->dispatcher->trigger_event('core.permissions_get_categories', $vars)); - - return $categories; - } - - public function get_types() - { $types = $this->types; - - /** - * Allows to specify additional permission types - * - * @event core.permissions_get_types - * @var array types Array with permission types (a_, u_, m_, etc.) - * @since 3.1-A1 - */ - $vars = array('types'); - extract($this->dispatcher->trigger_event('core.permissions_get_types', $vars)); - - return $types; - } - - public function get_permissions() - { $permissions = $this->permissions; /** - * Allows to specify additional permissions + * Allows to specify additional permission categories, types and permissions * - * @event core.permissions_get_types + * @event core.permissions + * @var array types Array with permission types (a_, u_, m_, etc.) + * @var array categories Array with permission categories (pm, post, settings, misc, etc.) * @var array permissions Array with permissions. Each Permission has the following layout: * 'acl_' => array( * 'lang' => 'Language Key with a Short description', // Optional, if not set, @@ -87,13 +63,56 @@ class phpbb_permissions * 'lang' => 'ACL_U_VIEWPROFILE', * 'cat' => 'profile', * ), - * * @since 3.1-A1 */ - $vars = array('permissions'); - extract($this->dispatcher->trigger_event('core.permissions_get_permissions', $vars)); + $vars = array('types', 'categories', 'permissions'); + extract($phpbb_dispatcher->trigger_event('core.permissions', $vars)); + + $this->categories = $categories; + $this->types = $types; + $this->permissions = $permissions; + } + + /** + * Returns an array with all the permission categories (pm, post, settings, misc, etc.) + * + * @return array Layout: cat-identifier => Language key + */ + public function get_categories() + { + return $this->categories; + } - return $permissions; + /** + * Returns an array with all the permission types (a_, u_, m_, etc.) + * + * @return array Layout: type-identifier => Language key + */ + public function get_types() + { + return $this->types; + } + + /** + * Returns an array with all the permissions. + * Each Permission has the following layout: + * 'acl_' => array( + * 'lang' => 'Language Key with a Short description', // Optional, if not set, + * // the permissions identifier 'acl_' is used with + * // all uppercase. + * 'cat' => 'Identifier of the category, the permission should be displayed in', + * ), + * Example: + * 'acl_u_viewprofile' => array( + * 'lang' => 'ACL_U_VIEWPROFILE', + * 'cat' => 'profile', + * ), + * + * @return array + */ + public function get_permissions() + { + return $this->permissions; } protected $types = array( -- cgit v1.2.1 From ce0a182c7fcd0c67020a44109440cd807bee2e82 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Fri, 28 Jun 2013 11:40:00 +0200 Subject: [ticket/11582] Add methods to return the language string PHPBB3-11582 --- phpBB/includes/acp/acp_permission_roles.php | 3 +-- phpBB/includes/acp/acp_permissions.php | 7 +++---- phpBB/includes/acp/auth.php | 6 ++---- phpBB/includes/permissions.php | 29 +++++++++++++++++++++++++++++ 4 files changed, 35 insertions(+), 10 deletions(-) (limited to 'phpBB') diff --git a/phpBB/includes/acp/acp_permission_roles.php b/phpBB/includes/acp/acp_permission_roles.php index 8a2798a90a..2a00301543 100644 --- a/phpBB/includes/acp/acp_permission_roles.php +++ b/phpBB/includes/acp/acp_permission_roles.php @@ -459,7 +459,6 @@ class acp_permission_roles global $template, $user, $phpbb_container; $permissions = $phpbb_container->get('acl.permissions'); - $permission_categories = $permissions->get_categories(); $content_array = $categories = array(); $key_sort_array = array(0); @@ -476,7 +475,7 @@ class acp_permission_roles foreach ($content_array as $cat => $cat_array) { $template->assign_block_vars('auth', array( - 'CAT_NAME' => $user->lang($permission_categories[$cat]), + 'CAT_NAME' => $permissions->get_lang_category($cat), 'S_YES' => ($cat_array['S_YES'] && !$cat_array['S_NEVER'] && !$cat_array['S_NO']) ? true : false, 'S_NEVER' => ($cat_array['S_NEVER'] && !$cat_array['S_YES'] && !$cat_array['S_NO']) ? true : false, diff --git a/phpBB/includes/acp/acp_permissions.php b/phpBB/includes/acp/acp_permissions.php index 9c5395c5b2..13e0f1c535 100644 --- a/phpBB/includes/acp/acp_permissions.php +++ b/phpBB/includes/acp/acp_permissions.php @@ -587,10 +587,9 @@ class acp_permissions */ function build_permission_dropdown($options, $default_option, $permission_scope) { - global $user, $auth, $phpbb_container; + global $auth, $phpbb_container; $permissions = $phpbb_container->get('acl.permissions'); - $permission_types = $permissions->get_types(); $s_dropdown_options = ''; foreach ($options as $setting) @@ -601,8 +600,8 @@ class acp_permissions } $selected = ($setting == $default_option) ? ' selected="selected"' : ''; - $l_setting = (isset($permission_types[$permission_scope][$setting])) ? $permission_types[$permission_scope][$setting] : $permission_types[$setting]; - $s_dropdown_options .= ''; + $l_setting = $permissions->get_lang_type($setting, $permission_scope); + $s_dropdown_options .= ''; } return $s_dropdown_options; diff --git a/phpBB/includes/acp/auth.php b/phpBB/includes/acp/auth.php index 03cc0c1705..7a7ccc0c50 100644 --- a/phpBB/includes/acp/auth.php +++ b/phpBB/includes/acp/auth.php @@ -1103,7 +1103,6 @@ class auth_admin extends phpbb_auth global $template, $user, $phpbb_admin_path, $phpEx, $phpbb_container; $permissions = $phpbb_container->get('acl.permissions'); - $permission_categories = $permissions->get_categories(); @reset($category_array); while (list($cat, $cat_array) = each($category_array)) @@ -1113,7 +1112,7 @@ class auth_admin extends phpbb_auth 'S_NEVER' => ($cat_array['S_NEVER'] && !$cat_array['S_YES'] && !$cat_array['S_NO']) ? true : false, 'S_NO' => ($cat_array['S_NO'] && !$cat_array['S_NEVER'] && !$cat_array['S_YES']) ? true : false, - 'CAT_NAME' => $user->lang($permission_categories[$cat]), + 'CAT_NAME' => $permissions->get_lang_category($cat), )); /* Sort permissions by name (more naturaly and user friendly than sorting by a primary key) @@ -1182,7 +1181,6 @@ class auth_admin extends phpbb_auth global $user, $phpbb_container; $permissions = $phpbb_container->get('acl.permissions'); - $permission_categories = $permissions->get_categories(); foreach ($key_sort_array as $forum_id) { @@ -1210,7 +1208,7 @@ class auth_admin extends phpbb_auth // Build our categories array if (!isset($categories[$cat])) { - $categories[$cat] = $user->lang($permission_categories[$cat]); + $categories[$cat] = $permissions->get_lang_category($cat); } // Build our content array diff --git a/phpBB/includes/permissions.php b/phpBB/includes/permissions.php index ddeadc825b..f3b2ab5da0 100644 --- a/phpBB/includes/permissions.php +++ b/phpBB/includes/permissions.php @@ -83,6 +83,16 @@ class phpbb_permissions return $this->categories; } + /** + * Returns the language string of a permission category + * + * @return array Language string + */ + public function get_lang_category($category) + { + return $this->user->lang($this->categories[$category]); + } + /** * Returns an array with all the permission types (a_, u_, m_, etc.) * @@ -93,6 +103,25 @@ class phpbb_permissions return $this->types; } + /** + * Returns the language string of a permission type + * + * @return array Language string + */ + public function get_lang_type($type, $scope = false) + { + if ($scope && isset($this->types[$scope][$type])) + { + $lang_key = $this->types[$scope][$type]; + } + else + { + $lang_key = $this->types[$type]; + } + + return $this->user->lang($lang_key); + } + /** * Returns an array with all the permissions. * Each Permission has the following layout: -- cgit v1.2.1 From 9c653341e4d747302bdde1273fd71199ca3b40ef Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Sat, 6 Jul 2013 13:37:59 +0200 Subject: [ticket/11582] Use new methods and remove duplicated entries PHPBB3-11582 --- phpBB/includes/acp/acp_permission_roles.php | 12 ++++---- phpBB/includes/acp/acp_permissions.php | 9 ++++-- phpBB/includes/acp/auth.php | 13 +++++---- phpBB/includes/permissions.php | 44 ++++++++++++++++++++++------- phpBB/language/en/acp/permissions_phpbb.php | 8 ------ 5 files changed, 54 insertions(+), 32 deletions(-) (limited to 'phpBB') diff --git a/phpBB/includes/acp/acp_permission_roles.php b/phpBB/includes/acp/acp_permission_roles.php index 2a00301543..f7c0494a0b 100644 --- a/phpBB/includes/acp/acp_permission_roles.php +++ b/phpBB/includes/acp/acp_permission_roles.php @@ -306,6 +306,9 @@ class acp_permission_roles trigger_error($user->lang['NO_ROLE_SELECTED'] . adm_back_link($this->u_action), E_USER_WARNING); } + global $phpbb_container; + $phpbb_permissions = $phpbb_container->get('acl.permissions'); + $template->assign_vars(array( 'S_EDIT' => true, @@ -314,9 +317,8 @@ class acp_permission_roles 'ROLE_NAME' => $role_row['role_name'], 'ROLE_DESCRIPTION' => $role_row['role_description'], - 'L_ACL_TYPE' => $user->lang['ACL_TYPE_' . strtoupper($permission_type)], - ) - ); + 'L_ACL_TYPE' => $phpbb_permissions->get_type_lang($permission_type), + )); // We need to fill the auth options array with ACL_NO options ;) $sql = 'SELECT auth_option_id, auth_option @@ -458,7 +460,7 @@ class acp_permission_roles { global $template, $user, $phpbb_container; - $permissions = $phpbb_container->get('acl.permissions'); + $phpbb_permissions = $phpbb_container->get('acl.permissions'); $content_array = $categories = array(); $key_sort_array = array(0); @@ -475,7 +477,7 @@ class acp_permission_roles foreach ($content_array as $cat => $cat_array) { $template->assign_block_vars('auth', array( - 'CAT_NAME' => $permissions->get_lang_category($cat), + 'CAT_NAME' => $phpbb_permissions->get_category_lang($cat), 'S_YES' => ($cat_array['S_YES'] && !$cat_array['S_NEVER'] && !$cat_array['S_NO']) ? true : false, 'S_NEVER' => ($cat_array['S_NEVER'] && !$cat_array['S_YES'] && !$cat_array['S_NO']) ? true : false, diff --git a/phpBB/includes/acp/acp_permissions.php b/phpBB/includes/acp/acp_permissions.php index 13e0f1c535..17c6561b65 100644 --- a/phpBB/includes/acp/acp_permissions.php +++ b/phpBB/includes/acp/acp_permissions.php @@ -510,9 +510,12 @@ class acp_permissions trigger_error($user->lang['ONLY_FORUM_DEFINED'] . adm_back_link($this->u_action), E_USER_WARNING); } + global $phpbb_container; + $phpbb_permissions = $phpbb_container->get('acl.permissions'); + $template->assign_vars(array( 'S_PERMISSION_DROPDOWN' => (sizeof($this->permission_dropdown) > 1) ? $this->build_permission_dropdown($this->permission_dropdown, $permission_type, $permission_scope) : false, - 'L_PERMISSION_TYPE' => $user->lang['ACL_TYPE_' . strtoupper($permission_type)], + 'L_PERMISSION_TYPE' => $phpbb_permissions->get_type_lang($permission_type), 'U_ACTION' => $this->u_action, 'S_HIDDEN_FIELDS' => $s_hidden_fields) @@ -589,7 +592,7 @@ class acp_permissions { global $auth, $phpbb_container; - $permissions = $phpbb_container->get('acl.permissions'); + $phpbb_permissions = $phpbb_container->get('acl.permissions'); $s_dropdown_options = ''; foreach ($options as $setting) @@ -600,7 +603,7 @@ class acp_permissions } $selected = ($setting == $default_option) ? ' selected="selected"' : ''; - $l_setting = $permissions->get_lang_type($setting, $permission_scope); + $l_setting = $phpbb_permissions->get_type_lang($setting, $permission_scope); $s_dropdown_options .= ''; } diff --git a/phpBB/includes/acp/auth.php b/phpBB/includes/acp/auth.php index 7a7ccc0c50..630deb991e 100644 --- a/phpBB/includes/acp/auth.php +++ b/phpBB/includes/acp/auth.php @@ -261,7 +261,8 @@ class auth_admin extends phpbb_auth */ function display_mask($mode, $permission_type, &$hold_ary, $user_mode = 'user', $local = false, $group_display = true) { - global $template, $user, $db, $phpbb_root_path, $phpEx; + global $template, $user, $db, $phpbb_root_path, $phpEx, $phpbb_container; + $phpbb_permissions = $phpbb_container->get('acl.permissions'); // Define names for template loops, might be able to be set $tpl_pmask = 'p_mask'; @@ -269,7 +270,7 @@ class auth_admin extends phpbb_auth $tpl_category = 'category'; $tpl_mask = 'mask'; - $l_acl_type = (isset($user->lang['ACL_TYPE_' . (($local) ? 'LOCAL' : 'GLOBAL') . '_' . strtoupper($permission_type)])) ? $user->lang['ACL_TYPE_' . (($local) ? 'LOCAL' : 'GLOBAL') . '_' . strtoupper($permission_type)] : 'ACL_TYPE_' . (($local) ? 'LOCAL' : 'GLOBAL') . '_' . strtoupper($permission_type); + $l_acl_type = $phpbb_permissions->get_type_lang($permission_type, (($local) ? 'local' : 'global')); // Allow trace for viewing permissions and in user mode $show_trace = ($mode == 'view' && $user_mode == 'user') ? true : false; @@ -1102,7 +1103,7 @@ class auth_admin extends phpbb_auth { global $template, $user, $phpbb_admin_path, $phpEx, $phpbb_container; - $permissions = $phpbb_container->get('acl.permissions'); + $phpbb_permissions = $phpbb_container->get('acl.permissions'); @reset($category_array); while (list($cat, $cat_array) = each($category_array)) @@ -1112,7 +1113,7 @@ class auth_admin extends phpbb_auth 'S_NEVER' => ($cat_array['S_NEVER'] && !$cat_array['S_YES'] && !$cat_array['S_NO']) ? true : false, 'S_NO' => ($cat_array['S_NO'] && !$cat_array['S_NEVER'] && !$cat_array['S_YES']) ? true : false, - 'CAT_NAME' => $permissions->get_lang_category($cat), + 'CAT_NAME' => $phpbb_permissions->get_category_lang($cat), )); /* Sort permissions by name (more naturaly and user friendly than sorting by a primary key) @@ -1180,7 +1181,7 @@ class auth_admin extends phpbb_auth { global $user, $phpbb_container; - $permissions = $phpbb_container->get('acl.permissions'); + $phpbb_permissions = $phpbb_container->get('acl.permissions'); foreach ($key_sort_array as $forum_id) { @@ -1208,7 +1209,7 @@ class auth_admin extends phpbb_auth // Build our categories array if (!isset($categories[$cat])) { - $categories[$cat] = $permissions->get_lang_category($cat); + $categories[$cat] = $phpbb_permissions->get_category_lang($cat); } // Build our content array diff --git a/phpBB/includes/permissions.php b/phpBB/includes/permissions.php index f3b2ab5da0..368fd7bc5f 100644 --- a/phpBB/includes/permissions.php +++ b/phpBB/includes/permissions.php @@ -86,9 +86,9 @@ class phpbb_permissions /** * Returns the language string of a permission category * - * @return array Language string + * @return string Language string */ - public function get_lang_category($category) + public function get_category_lang($category) { return $this->user->lang($this->categories[$category]); } @@ -106,18 +106,22 @@ class phpbb_permissions /** * Returns the language string of a permission type * - * @return array Language string + * @return string Language string */ - public function get_lang_type($type, $scope = false) + public function get_type_lang($type, $scope = false) { if ($scope && isset($this->types[$scope][$type])) { $lang_key = $this->types[$scope][$type]; } - else + else if (isset($this->types[$type])) { $lang_key = $this->types[$type]; } + else + { + $lang_key = 'ACL_TYPE_' . strtoupper(($scope) ? $scope . '_' . $type : $type); + } return $this->user->lang($lang_key); } @@ -144,13 +148,33 @@ class phpbb_permissions return $this->permissions; } + /** + * Returns the category of a permission + * + * @return string + */ + public function get_permission_category($permission) + { + return (isset($this->permissions[$permission]['cat'])) ? $this->permissions[$permission]['cat'] : 'misc'; + } + + /** + * Returns the language string of a permission + * + * @return string Language string + */ + public function get_permission_lang($permission) + { + return (isset($this->permissions[$permission]['lang'])) ? $this->user->lang($this->permissions[$permission]['lang']) : $this->user->lang('ACL_' . strtoupper($permission)); + } + protected $types = array( - 'u_' => 'ACL_TYPE_USER', - 'a_' => 'ACL_TYPE_ADMIN', - 'm_' => 'ACL_TYPE_MODERATOR', - 'f_' => 'ACL_TYPE_FORUM', + 'u_' => 'ACL_TYPE_U_', + 'a_' => 'ACL_TYPE_A_', + 'm_' => 'ACL_TYPE_M_', + 'f_' => 'ACL_TYPE_F_', 'global' => array( - 'm_' => 'ACL_TYPE_GLOBAL_MODERATOR', + 'm_' => 'ACL_TYPE_GLOBAL_M_', ), ); diff --git a/phpBB/language/en/acp/permissions_phpbb.php b/phpBB/language/en/acp/permissions_phpbb.php index 0a669a4b8d..4f2c9067d2 100644 --- a/phpBB/language/en/acp/permissions_phpbb.php +++ b/phpBB/language/en/acp/permissions_phpbb.php @@ -63,7 +63,6 @@ if (empty($lang) || !is_array($lang)) * */ -// Define categories and permission types $lang = array_merge($lang, array( 'ACL_CAT_ACTIONS' => 'Actions', 'ACL_CAT_CONTENT' => 'Content', @@ -79,13 +78,6 @@ $lang = array_merge($lang, array( 'ACL_CAT_SETTINGS' => 'Settings', 'ACL_CAT_TOPIC_ACTIONS' => 'Topic actions', 'ACL_CAT_USER_GROUP' => 'Users & Groups', - - - 'ACL_TYPE_USER' => 'User permissions', - 'ACL_TYPE_ADMIN' => 'Admin permissions', - 'ACL_TYPE_MODERATOR' => 'Moderator permissions', - 'ACL_TYPE_FORUM' => 'Forum permissions', - 'ACL_TYPE_GLOBAL_MODERATOR' => 'Global moderator permissions', )); // User Permissions -- cgit v1.2.1 From 22ba3de1a2902b09cb00748664ad2bf328f37734 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Sat, 6 Jul 2013 13:52:09 +0200 Subject: [ticket/11582] Use member isntead of a new variable everytime PHPBB3-11582 --- phpBB/includes/acp/acp_permissions.php | 20 +++++++++----------- phpBB/includes/permissions.php | 2 +- 2 files changed, 10 insertions(+), 12 deletions(-) (limited to 'phpBB') diff --git a/phpBB/includes/acp/acp_permissions.php b/phpBB/includes/acp/acp_permissions.php index 17c6561b65..ed7159996a 100644 --- a/phpBB/includes/acp/acp_permissions.php +++ b/phpBB/includes/acp/acp_permissions.php @@ -22,15 +22,18 @@ class acp_permissions { var $u_action; var $permission_dropdown; + protected $permissions; function main($id, $mode) { - global $db, $user, $auth, $template, $cache; + global $db, $user, $auth, $template, $cache, $phpbb_container; global $config, $phpbb_root_path, $phpbb_admin_path, $phpEx; include_once($phpbb_root_path . 'includes/functions_user.' . $phpEx); include_once($phpbb_root_path . 'includes/acp/auth.' . $phpEx); + $this->permissions = $phpbb_container->get('acl.permissions'); + $auth_admin = new auth_admin(); $user->add_lang('acp/permissions'); @@ -49,7 +52,7 @@ class acp_permissions if ($user_id && isset($auth_admin->acl_options['id'][$permission]) && $auth->acl_get('a_viewauth')) { - $this->page_title = sprintf($user->lang['TRACE_PERMISSION'], $user->lang['acl_' . $permission]['lang']); + $this->page_title = sprintf($user->lang['TRACE_PERMISSION'], $this->permissions->get_permission_lang($permission)); $this->permission_trace($user_id, $forum_id, $permission); return; } @@ -510,12 +513,9 @@ class acp_permissions trigger_error($user->lang['ONLY_FORUM_DEFINED'] . adm_back_link($this->u_action), E_USER_WARNING); } - global $phpbb_container; - $phpbb_permissions = $phpbb_container->get('acl.permissions'); - $template->assign_vars(array( 'S_PERMISSION_DROPDOWN' => (sizeof($this->permission_dropdown) > 1) ? $this->build_permission_dropdown($this->permission_dropdown, $permission_type, $permission_scope) : false, - 'L_PERMISSION_TYPE' => $phpbb_permissions->get_type_lang($permission_type), + 'L_PERMISSION_TYPE' => $this->permissions->get_type_lang($permission_type), 'U_ACTION' => $this->u_action, 'S_HIDDEN_FIELDS' => $s_hidden_fields) @@ -590,9 +590,7 @@ class acp_permissions */ function build_permission_dropdown($options, $default_option, $permission_scope) { - global $auth, $phpbb_container; - - $phpbb_permissions = $phpbb_container->get('acl.permissions'); + global $auth; $s_dropdown_options = ''; foreach ($options as $setting) @@ -603,7 +601,7 @@ class acp_permissions } $selected = ($setting == $default_option) ? ' selected="selected"' : ''; - $l_setting = $phpbb_permissions->get_type_lang($setting, $permission_scope); + $l_setting = $this->permissions->get_type_lang($setting, $permission_scope); $s_dropdown_options .= ''; } @@ -984,7 +982,7 @@ class acp_permissions $back = request_var('back', 0); $template->assign_vars(array( - 'PERMISSION' => $user->lang['acl_' . $permission]['lang'], + 'PERMISSION' => $this->permissions->get_permission_lang($permission), 'PERMISSION_USERNAME' => $userdata['username'], 'FORUM_NAME' => $forum_name, diff --git a/phpBB/includes/permissions.php b/phpBB/includes/permissions.php index 368fd7bc5f..a450b12aed 100644 --- a/phpBB/includes/permissions.php +++ b/phpBB/includes/permissions.php @@ -165,7 +165,7 @@ class phpbb_permissions */ public function get_permission_lang($permission) { - return (isset($this->permissions[$permission]['lang'])) ? $this->user->lang($this->permissions[$permission]['lang']) : $this->user->lang('ACL_' . strtoupper($permission)); + return (isset($this->permissions['acl_' . $permission]['lang'])) ? $this->user->lang($this->permissions['acl_' . $permission]['lang']) : $this->user->lang('ACL_' . strtoupper($permission)); } protected $types = array( -- cgit v1.2.1 From aadff800dcadfdb21f3188feba754f0e93669781 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Sat, 6 Jul 2013 13:57:58 +0200 Subject: [ticket/11582] Remove left over calls to lang['acl_*'] PHPBB3-11582 --- phpBB/includes/acp/acp_permission_roles.php | 2 +- phpBB/includes/acp/auth.php | 18 +++++------------- 2 files changed, 6 insertions(+), 14 deletions(-) (limited to 'phpBB') diff --git a/phpBB/includes/acp/acp_permission_roles.php b/phpBB/includes/acp/acp_permission_roles.php index f7c0494a0b..7c972c74ba 100644 --- a/phpBB/includes/acp/acp_permission_roles.php +++ b/phpBB/includes/acp/acp_permission_roles.php @@ -492,7 +492,7 @@ class acp_permission_roles 'S_NO' => ($allowed == ACL_NO) ? true : false, 'FIELD_NAME' => $permission, - 'PERMISSION' => $user->lang['acl_' . $permission]['lang']) + 'PERMISSION' => $phpbb_permissions->get_permission_lang($permission) ); } } diff --git a/phpBB/includes/acp/auth.php b/phpBB/includes/acp/auth.php index 630deb991e..4ade9cab13 100644 --- a/phpBB/includes/acp/auth.php +++ b/phpBB/includes/acp/auth.php @@ -1148,8 +1148,8 @@ class auth_admin extends phpbb_auth 'U_TRACE' => ($show_trace) ? append_sid("{$phpbb_admin_path}index.$phpEx", "i=permissions&mode=trace&u=$ug_id&f=$forum_id&auth=$permission") : '', 'UA_TRACE' => ($show_trace) ? append_sid("{$phpbb_admin_path}index.$phpEx", "i=permissions&mode=trace&u=$ug_id&f=$forum_id&auth=$permission", false) : '', - 'PERMISSION' => $user->lang['acl_' . $permission]['lang']) - ); + 'PERMISSION' => $phpbb_permissions->get_permission_lang($permission), + )); } else { @@ -1166,8 +1166,8 @@ class auth_admin extends phpbb_auth 'U_TRACE' => ($show_trace) ? append_sid("{$phpbb_admin_path}index.$phpEx", "i=permissions&mode=trace&u=$ug_id&f=$forum_id&auth=$permission") : '', 'UA_TRACE' => ($show_trace) ? append_sid("{$phpbb_admin_path}index.$phpEx", "i=permissions&mode=trace&u=$ug_id&f=$forum_id&auth=$permission", false) : '', - 'PERMISSION' => $user->lang['acl_' . $permission]['lang']) - ); + 'PERMISSION' => $phpbb_permissions->get_permission_lang($permission), + )); } } } @@ -1196,15 +1196,7 @@ class auth_admin extends phpbb_auth @reset($permissions); while (list($permission, $auth_setting) = each($permissions)) { - if (!isset($user->lang['acl_' . $permission])) - { - $user->lang['acl_' . $permission] = array( - 'cat' => 'misc', - 'lang' => '{ acl_' . $permission . ' }' - ); - } - - $cat = $user->lang['acl_' . $permission]['cat']; + $cat = $phpbb_permissions->get_permission_category($permission); // Build our categories array if (!isset($categories[$cat])) -- cgit v1.2.1 From 0e86c0247381b141cba53b6ab3fbdba276c521e3 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Sat, 6 Jul 2013 15:55:25 +0200 Subject: [ticket/11582] Split permission language strings from logic PHPBB3-11582 --- phpBB/includes/permissions.php | 252 +++++++++++++-------------- phpBB/language/en/acp/permissions_phpbb.php | 253 +++++++++++++--------------- 2 files changed, 247 insertions(+), 258 deletions(-) (limited to 'phpBB') diff --git a/phpBB/includes/permissions.php b/phpBB/includes/permissions.php index a450b12aed..4cb3356c53 100644 --- a/phpBB/includes/permissions.php +++ b/phpBB/includes/permissions.php @@ -197,139 +197,139 @@ class phpbb_permissions protected $permissions = array( // User Permissions - 'acl_u_viewprofile' => array('lang' => 'Can view profiles, memberlist and online list', 'cat' => 'profile'), - 'acl_u_chgname' => array('lang' => 'Can change username', 'cat' => 'profile'), - 'acl_u_chgpasswd' => array('lang' => 'Can change password', 'cat' => 'profile'), - 'acl_u_chgemail' => array('lang' => 'Can change email address', 'cat' => 'profile'), - 'acl_u_chgavatar' => array('lang' => 'Can change avatar', 'cat' => 'profile'), - 'acl_u_chggrp' => array('lang' => 'Can change default usergroup', 'cat' => 'profile'), - 'acl_u_chgprofileinfo' => array('lang' => 'Can change profile field information', 'cat' => 'profile'), - - 'acl_u_attach' => array('lang' => 'Can attach files', 'cat' => 'post'), - 'acl_u_download' => array('lang' => 'Can download files', 'cat' => 'post'), - 'acl_u_savedrafts' => array('lang' => 'Can save drafts', 'cat' => 'post'), - 'acl_u_chgcensors' => array('lang' => 'Can disable word censors', 'cat' => 'post'), - 'acl_u_sig' => array('lang' => 'Can use signature', 'cat' => 'post'), - - 'acl_u_sendpm' => array('lang' => 'Can send private messages', 'cat' => 'pm'), - 'acl_u_masspm' => array('lang' => 'Can send messages to multiple users', 'cat' => 'pm'), - 'acl_u_masspm_group'=> array('lang' => 'Can send messages to groups', 'cat' => 'pm'), - 'acl_u_readpm' => array('lang' => 'Can read private messages', 'cat' => 'pm'), - 'acl_u_pm_edit' => array('lang' => 'Can edit own private messages', 'cat' => 'pm'), - 'acl_u_pm_delete' => array('lang' => 'Can remove private messages from own folder', 'cat' => 'pm'), - 'acl_u_pm_forward' => array('lang' => 'Can forward private messages', 'cat' => 'pm'), - 'acl_u_pm_emailpm' => array('lang' => 'Can email private messages', 'cat' => 'pm'), - 'acl_u_pm_printpm' => array('lang' => 'Can print private messages', 'cat' => 'pm'), - 'acl_u_pm_attach' => array('lang' => 'Can attach files in private messages', 'cat' => 'pm'), - 'acl_u_pm_download' => array('lang' => 'Can download files in private messages', 'cat' => 'pm'), - 'acl_u_pm_bbcode' => array('lang' => 'Can use BBCode in private messages', 'cat' => 'pm'), - 'acl_u_pm_smilies' => array('lang' => 'Can use smilies in private messages', 'cat' => 'pm'), - 'acl_u_pm_img' => array('lang' => 'Can use [img] BBCode tag in private messages', 'cat' => 'pm'), - 'acl_u_pm_flash' => array('lang' => 'Can use [flash] BBCode tag in private messages', 'cat' => 'pm'), - - 'acl_u_sendemail' => array('lang' => 'Can send emails', 'cat' => 'misc'), - 'acl_u_sendim' => array('lang' => 'Can send instant messages', 'cat' => 'misc'), - 'acl_u_ignoreflood' => array('lang' => 'Can ignore flood limit', 'cat' => 'misc'), - 'acl_u_hideonline' => array('lang' => 'Can hide online status', 'cat' => 'misc'), - 'acl_u_viewonline' => array('lang' => 'Can view hidden online users', 'cat' => 'misc'), - 'acl_u_search' => array('lang' => 'Can search board', 'cat' => 'misc'), + 'acl_u_viewprofile' => array('lang' => 'ACL_U_VIEWPROFILE', 'cat' => 'profile'), + 'acl_u_chgname' => array('lang' => 'ACL_U_CHGNAME', 'cat' => 'profile'), + 'acl_u_chgpasswd' => array('lang' => 'ACL_U_CHGPASSWD', 'cat' => 'profile'), + 'acl_u_chgemail' => array('lang' => 'ACL_U_CHGEMAIL', 'cat' => 'profile'), + 'acl_u_chgavatar' => array('lang' => 'ACL_U_CHGAVATAR', 'cat' => 'profile'), + 'acl_u_chggrp' => array('lang' => 'ACL_U_CHGGRP', 'cat' => 'profile'), + 'acl_u_chgprofileinfo' => array('lang' => 'ACL_U_CHGPROFILEINFO', 'cat' => 'profile'), + + 'acl_u_attach' => array('lang' => 'ACL_U_ATTACH', 'cat' => 'post'), + 'acl_u_download' => array('lang' => 'ACL_U_DOWNLOAD', 'cat' => 'post'), + 'acl_u_savedrafts' => array('lang' => 'ACL_U_SAVEDRAFTS', 'cat' => 'post'), + 'acl_u_chgcensors' => array('lang' => 'ACL_U_CHGCENSORS', 'cat' => 'post'), + 'acl_u_sig' => array('lang' => 'ACL_U_SIG', 'cat' => 'post'), + + 'acl_u_sendpm' => array('lang' => 'ACL_U_SENDPM', 'cat' => 'pm'), + 'acl_u_masspm' => array('lang' => 'ACL_U_MASSPM', 'cat' => 'pm'), + 'acl_u_masspm_group'=> array('lang' => 'ACL_U_MASSPM_GROUP', 'cat' => 'pm'), + 'acl_u_readpm' => array('lang' => 'ACL_U_READPM', 'cat' => 'pm'), + 'acl_u_pm_edit' => array('lang' => 'ACL_U_PM_EDIT', 'cat' => 'pm'), + 'acl_u_pm_delete' => array('lang' => 'ACL_U_PM_DELETE', 'cat' => 'pm'), + 'acl_u_pm_forward' => array('lang' => 'ACL_U_PM_FORWARD', 'cat' => 'pm'), + 'acl_u_pm_emailpm' => array('lang' => 'ACL_U_PM_EMAILPM', 'cat' => 'pm'), + 'acl_u_pm_printpm' => array('lang' => 'ACL_U_PM_PRINTPM', 'cat' => 'pm'), + 'acl_u_pm_attach' => array('lang' => 'ACL_U_PM_ATTACH', 'cat' => 'pm'), + 'acl_u_pm_download' => array('lang' => 'ACL_U_PM_DOWNLOAD', 'cat' => 'pm'), + 'acl_u_pm_bbcode' => array('lang' => 'ACL_U_PM_BBCODE', 'cat' => 'pm'), + 'acl_u_pm_smilies' => array('lang' => 'ACL_U_PM_SMILIES', 'cat' => 'pm'), + 'acl_u_pm_img' => array('lang' => 'ACL_U_PM_IMG', 'cat' => 'pm'), + 'acl_u_pm_flash' => array('lang' => 'ACL_U_PM_FLASH', 'cat' => 'pm'), + + 'acl_u_sendemail' => array('lang' => 'ACL_U_SENDEMAIL', 'cat' => 'misc'), + 'acl_u_sendim' => array('lang' => 'ACL_U_SENDIM', 'cat' => 'misc'), + 'acl_u_ignoreflood' => array('lang' => 'ACL_U_IGNOREFLOOD', 'cat' => 'misc'), + 'acl_u_hideonline' => array('lang' => 'ACL_U_HIDEONLINE', 'cat' => 'misc'), + 'acl_u_viewonline' => array('lang' => 'ACL_U_VIEWONLINE', 'cat' => 'misc'), + 'acl_u_search' => array('lang' => 'ACL_U_SEARCH', 'cat' => 'misc'), // Forum Permissions - 'acl_f_list' => array('lang' => 'Can see forum', 'cat' => 'actions'), - 'acl_f_read' => array('lang' => 'Can read forum', 'cat' => 'actions'), - 'acl_f_search' => array('lang' => 'Can search the forum', 'cat' => 'actions'), - 'acl_f_subscribe' => array('lang' => 'Can subscribe forum', 'cat' => 'actions'), - 'acl_f_print' => array('lang' => 'Can print topics', 'cat' => 'actions'), - 'acl_f_email' => array('lang' => 'Can email topics', 'cat' => 'actions'), - 'acl_f_bump' => array('lang' => 'Can bump topics', 'cat' => 'actions'), - 'acl_f_user_lock' => array('lang' => 'Can lock own topics', 'cat' => 'actions'), - 'acl_f_download' => array('lang' => 'Can download files', 'cat' => 'actions'), - 'acl_f_report' => array('lang' => 'Can report posts', 'cat' => 'actions'), - - 'acl_f_post' => array('lang' => 'Can start new topics', 'cat' => 'post'), - 'acl_f_sticky' => array('lang' => 'Can post stickies', 'cat' => 'post'), - 'acl_f_announce' => array('lang' => 'Can post announcements', 'cat' => 'post'), - 'acl_f_reply' => array('lang' => 'Can reply to topics', 'cat' => 'post'), - 'acl_f_edit' => array('lang' => 'Can edit own posts', 'cat' => 'post'), - 'acl_f_delete' => array('lang' => 'Can delete own posts', 'cat' => 'post'), - 'acl_f_ignoreflood' => array('lang' => 'Can ignore flood limit', 'cat' => 'post'), - 'acl_f_postcount' => array('lang' => 'Increment post counter
Please note that this setting only affects new posts.', 'cat' => 'post'), - 'acl_f_noapprove' => array('lang' => 'Can post without approval', 'cat' => 'post'), - - 'acl_f_attach' => array('lang' => 'Can attach files', 'cat' => 'content'), - 'acl_f_icons' => array('lang' => 'Can use topic/post icons', 'cat' => 'content'), - 'acl_f_bbcode' => array('lang' => 'Can use BBCode', 'cat' => 'content'), - 'acl_f_flash' => array('lang' => 'Can use [flash] BBCode tag', 'cat' => 'content'), - 'acl_f_img' => array('lang' => 'Can use [img] BBCode tag', 'cat' => 'content'), - 'acl_f_sigs' => array('lang' => 'Can use signatures', 'cat' => 'content'), - 'acl_f_smilies' => array('lang' => 'Can use smilies', 'cat' => 'content'), - - 'acl_f_poll' => array('lang' => 'Can create polls', 'cat' => 'polls'), - 'acl_f_vote' => array('lang' => 'Can vote in polls', 'cat' => 'polls'), - 'acl_f_votechg' => array('lang' => 'Can change existing vote', 'cat' => 'polls'), + 'acl_f_list' => array('lang' => 'ACL_F_LIST', 'cat' => 'actions'), + 'acl_f_read' => array('lang' => 'ACL_F_READ', 'cat' => 'actions'), + 'acl_f_search' => array('lang' => 'ACL_F_SEARCH', 'cat' => 'actions'), + 'acl_f_subscribe' => array('lang' => 'ACL_F_SUBSCRIBE', 'cat' => 'actions'), + 'acl_f_print' => array('lang' => 'ACL_F_PRINT', 'cat' => 'actions'), + 'acl_f_email' => array('lang' => 'ACL_F_EMAIL', 'cat' => 'actions'), + 'acl_f_bump' => array('lang' => 'ACL_F_BUMP', 'cat' => 'actions'), + 'acl_f_user_lock' => array('lang' => 'ACL_F_USER_LOCK', 'cat' => 'actions'), + 'acl_f_download' => array('lang' => 'ACL_F_DOWNLOAD', 'cat' => 'actions'), + 'acl_f_report' => array('lang' => 'ACL_F_REPORT', 'cat' => 'actions'), + + 'acl_f_post' => array('lang' => 'ACL_F_POST', 'cat' => 'post'), + 'acl_f_sticky' => array('lang' => 'ACL_F_STICKY', 'cat' => 'post'), + 'acl_f_announce' => array('lang' => 'ACL_F_ANNOUNCE', 'cat' => 'post'), + 'acl_f_reply' => array('lang' => 'ACL_F_REPLY', 'cat' => 'post'), + 'acl_f_edit' => array('lang' => 'ACL_F_EDIT', 'cat' => 'post'), + 'acl_f_delete' => array('lang' => 'ACL_F_DELETE', 'cat' => 'post'), + 'acl_f_ignoreflood' => array('lang' => 'ACL_F_IGNOREFLOOD', 'cat' => 'post'), + 'acl_f_postcount' => array('lang' => 'ACL_F_POSTCOUNT', 'cat' => 'post'), + 'acl_f_noapprove' => array('lang' => 'ACL_F_NOAPPROVE', 'cat' => 'post'), + + 'acl_f_attach' => array('lang' => 'ACL_F_ATTACH', 'cat' => 'content'), + 'acl_f_icons' => array('lang' => 'ACL_F_ICONS', 'cat' => 'content'), + 'acl_f_bbcode' => array('lang' => 'ACL_F_BBCODE', 'cat' => 'content'), + 'acl_f_flash' => array('lang' => 'ACL_F_FLASH', 'cat' => 'content'), + 'acl_f_img' => array('lang' => 'ACL_F_IMG', 'cat' => 'content'), + 'acl_f_sigs' => array('lang' => 'ACL_F_SIGS', 'cat' => 'content'), + 'acl_f_smilies' => array('lang' => 'ACL_F_SMILIES', 'cat' => 'content'), + + 'acl_f_poll' => array('lang' => 'ACL_F_POLL', 'cat' => 'polls'), + 'acl_f_vote' => array('lang' => 'ACL_F_VOTE', 'cat' => 'polls'), + 'acl_f_votechg' => array('lang' => 'ACL_F_VOTECHG', 'cat' => 'polls'), // Moderator Permissions - 'acl_m_edit' => array('lang' => 'Can edit posts', 'cat' => 'post_actions'), - 'acl_m_delete' => array('lang' => 'Can delete posts', 'cat' => 'post_actions'), - 'acl_m_approve' => array('lang' => 'Can approve posts', 'cat' => 'post_actions'), - 'acl_m_report' => array('lang' => 'Can close and delete reports', 'cat' => 'post_actions'), - 'acl_m_chgposter' => array('lang' => 'Can change post author', 'cat' => 'post_actions'), + 'acl_m_edit' => array('lang' => 'ACL_M_EDIT', 'cat' => 'post_actions'), + 'acl_m_delete' => array('lang' => 'ACL_M_DELETE', 'cat' => 'post_actions'), + 'acl_m_approve' => array('lang' => 'ACL_M_APPROVE', 'cat' => 'post_actions'), + 'acl_m_report' => array('lang' => 'ACL_M_REPORT', 'cat' => 'post_actions'), + 'acl_m_chgposter' => array('lang' => 'ACL_M_CHGPOSTER', 'cat' => 'post_actions'), - 'acl_m_move' => array('lang' => 'Can move topics', 'cat' => 'topic_actions'), - 'acl_m_lock' => array('lang' => 'Can lock topics', 'cat' => 'topic_actions'), - 'acl_m_split' => array('lang' => 'Can split topics', 'cat' => 'topic_actions'), - 'acl_m_merge' => array('lang' => 'Can merge topics', 'cat' => 'topic_actions'), + 'acl_m_move' => array('lang' => 'ACL_M_MOVE', 'cat' => 'topic_actions'), + 'acl_m_lock' => array('lang' => 'ACL_M_LOCK', 'cat' => 'topic_actions'), + 'acl_m_split' => array('lang' => 'ACL_M_SPLIT', 'cat' => 'topic_actions'), + 'acl_m_merge' => array('lang' => 'ACL_M_MERGE', 'cat' => 'topic_actions'), - 'acl_m_info' => array('lang' => 'Can view post details', 'cat' => 'misc'), - 'acl_m_warn' => array('lang' => 'Can issue warnings
This setting is only assigned globally. It is not forum based.', 'cat' => 'misc'), // This moderator setting is only global (and not local) - 'acl_m_ban' => array('lang' => 'Can manage bans
This setting is only assigned globally. It is not forum based.', 'cat' => 'misc'), // This moderator setting is only global (and not local) + 'acl_m_info' => array('lang' => 'ACL_M_INFO', 'cat' => 'misc'), + 'acl_m_warn' => array('lang' => 'ACL_M_WARN', 'cat' => 'misc'), + 'acl_m_ban' => array('lang' => 'ACL_M_BAN', 'cat' => 'misc'), // Admin Permissions - 'acl_a_board' => array('lang' => 'Can alter board settings/check for updates', 'cat' => 'settings'), - 'acl_a_server' => array('lang' => 'Can alter server/communication settings', 'cat' => 'settings'), - 'acl_a_jabber' => array('lang' => 'Can alter Jabber settings', 'cat' => 'settings'), - 'acl_a_phpinfo' => array('lang' => 'Can view php settings', 'cat' => 'settings'), - - 'acl_a_forum' => array('lang' => 'Can manage forums', 'cat' => 'forums'), - 'acl_a_forumadd' => array('lang' => 'Can add new forums', 'cat' => 'forums'), - 'acl_a_forumdel' => array('lang' => 'Can delete forums', 'cat' => 'forums'), - 'acl_a_prune' => array('lang' => 'Can prune forums', 'cat' => 'forums'), - - 'acl_a_icons' => array('lang' => 'Can alter topic/post icons and smilies', 'cat' => 'posting'), - 'acl_a_words' => array('lang' => 'Can alter word censors', 'cat' => 'posting'), - 'acl_a_bbcode' => array('lang' => 'Can define BBCode tags', 'cat' => 'posting'), - 'acl_a_attach' => array('lang' => 'Can alter attachment related settings', 'cat' => 'posting'), - - 'acl_a_user' => array('lang' => 'Can manage users
This also includes seeing the users browser agent within the viewonline list.', 'cat' => 'user_group'), - 'acl_a_userdel' => array('lang' => 'Can delete/prune users', 'cat' => 'user_group'), - 'acl_a_group' => array('lang' => 'Can manage groups', 'cat' => 'user_group'), - 'acl_a_groupadd' => array('lang' => 'Can add new groups', 'cat' => 'user_group'), - 'acl_a_groupdel' => array('lang' => 'Can delete groups', 'cat' => 'user_group'), - 'acl_a_ranks' => array('lang' => 'Can manage ranks', 'cat' => 'user_group'), - 'acl_a_profile' => array('lang' => 'Can manage custom profile fields', 'cat' => 'user_group'), - 'acl_a_names' => array('lang' => 'Can manage disallowed names', 'cat' => 'user_group'), - 'acl_a_ban' => array('lang' => 'Can manage bans', 'cat' => 'user_group'), - - 'acl_a_viewauth' => array('lang' => 'Can view permission masks', 'cat' => 'permissions'), - 'acl_a_authgroups' => array('lang' => 'Can alter permissions for individual groups', 'cat' => 'permissions'), - 'acl_a_authusers' => array('lang' => 'Can alter permissions for individual users', 'cat' => 'permissions'), - 'acl_a_fauth' => array('lang' => 'Can alter forum permission class', 'cat' => 'permissions'), - 'acl_a_mauth' => array('lang' => 'Can alter moderator permission class', 'cat' => 'permissions'), - 'acl_a_aauth' => array('lang' => 'Can alter admin permission class', 'cat' => 'permissions'), - 'acl_a_uauth' => array('lang' => 'Can alter user permission class', 'cat' => 'permissions'), - 'acl_a_roles' => array('lang' => 'Can manage roles', 'cat' => 'permissions'), - 'acl_a_switchperm' => array('lang' => 'Can use others permissions', 'cat' => 'permissions'), - - 'acl_a_styles' => array('lang' => 'Can manage styles', 'cat' => 'misc'), - 'acl_a_extensions' => array('lang' => 'Can manage extensions', 'cat' => 'misc'), - 'acl_a_viewlogs' => array('lang' => 'Can view logs', 'cat' => 'misc'), - 'acl_a_clearlogs' => array('lang' => 'Can clear logs', 'cat' => 'misc'), - 'acl_a_modules' => array('lang' => 'Can manage modules', 'cat' => 'misc'), - 'acl_a_language' => array('lang' => 'Can manage language packs', 'cat' => 'misc'), - 'acl_a_email' => array('lang' => 'Can send mass email', 'cat' => 'misc'), - 'acl_a_bots' => array('lang' => 'Can manage bots', 'cat' => 'misc'), - 'acl_a_reasons' => array('lang' => 'Can manage report/denial reasons', 'cat' => 'misc'), - 'acl_a_backup' => array('lang' => 'Can backup/restore database', 'cat' => 'misc'), - 'acl_a_search' => array('lang' => 'Can manage search backends and settings', 'cat' => 'misc'), + 'acl_a_board' => array('lang' => 'ACL_A_BOARD', 'cat' => 'settings'), + 'acl_a_server' => array('lang' => 'ACL_A_SERVER', 'cat' => 'settings'), + 'acl_a_jabber' => array('lang' => 'ACL_A_JABBER', 'cat' => 'settings'), + 'acl_a_phpinfo' => array('lang' => 'ACL_A_PHPINFO', 'cat' => 'settings'), + + 'acl_a_forum' => array('lang' => 'ACL_A_FORUM', 'cat' => 'forums'), + 'acl_a_forumadd' => array('lang' => 'ACL_A_FORUMADD', 'cat' => 'forums'), + 'acl_a_forumdel' => array('lang' => 'ACL_A_FORUMDEL', 'cat' => 'forums'), + 'acl_a_prune' => array('lang' => 'ACL_A_PRUNE', 'cat' => 'forums'), + + 'acl_a_icons' => array('lang' => 'ACL_A_ICONS', 'cat' => 'posting'), + 'acl_a_words' => array('lang' => 'ACL_A_WORDS', 'cat' => 'posting'), + 'acl_a_bbcode' => array('lang' => 'ACL_A_BBCODE', 'cat' => 'posting'), + 'acl_a_attach' => array('lang' => 'ACL_A_ATTACH', 'cat' => 'posting'), + + 'acl_a_user' => array('lang' => 'ACL_A_USER', 'cat' => 'user_group'), + 'acl_a_userdel' => array('lang' => 'ACL_A_USERDEL', 'cat' => 'user_group'), + 'acl_a_group' => array('lang' => 'ACL_A_GROUP', 'cat' => 'user_group'), + 'acl_a_groupadd' => array('lang' => 'ACL_A_GROUPADD', 'cat' => 'user_group'), + 'acl_a_groupdel' => array('lang' => 'ACL_A_GROUPDEL', 'cat' => 'user_group'), + 'acl_a_ranks' => array('lang' => 'ACL_A_RANKS', 'cat' => 'user_group'), + 'acl_a_profile' => array('lang' => 'ACL_A_PROFILE', 'cat' => 'user_group'), + 'acl_a_names' => array('lang' => 'ACL_A_NAMES', 'cat' => 'user_group'), + 'acl_a_ban' => array('lang' => 'ACL_A_BAN', 'cat' => 'user_group'), + + 'acl_a_viewauth' => array('lang' => 'ACL_A_VIEWAUTH', 'cat' => 'permissions'), + 'acl_a_authgroups' => array('lang' => 'ACL_A_AUTHGROUPS', 'cat' => 'permissions'), + 'acl_a_authusers' => array('lang' => 'ACL_A_AUTHUSERS', 'cat' => 'permissions'), + 'acl_a_fauth' => array('lang' => 'ACL_A_FAUTH', 'cat' => 'permissions'), + 'acl_a_mauth' => array('lang' => 'ACL_A_MAUTH', 'cat' => 'permissions'), + 'acl_a_aauth' => array('lang' => 'ACL_A_AAUTH', 'cat' => 'permissions'), + 'acl_a_uauth' => array('lang' => 'ACL_A_UAUTH', 'cat' => 'permissions'), + 'acl_a_roles' => array('lang' => 'ACL_A_ROLES', 'cat' => 'permissions'), + 'acl_a_switchperm' => array('lang' => 'ACL_A_SWITCHPERM', 'cat' => 'permissions'), + + 'acl_a_styles' => array('lang' => 'ACL_A_STYLES', 'cat' => 'misc'), + 'acl_a_extensions' => array('lang' => 'ACL_A_EXTENSIONS', 'cat' => 'misc'), + 'acl_a_viewlogs' => array('lang' => 'ACL_A_VIEWLOGS', 'cat' => 'misc'), + 'acl_a_clearlogs' => array('lang' => 'ACL_A_CLEARLOGS', 'cat' => 'misc'), + 'acl_a_modules' => array('lang' => 'ACL_A_MODULES', 'cat' => 'misc'), + 'acl_a_language' => array('lang' => 'ACL_A_LANGUAGE', 'cat' => 'misc'), + 'acl_a_email' => array('lang' => 'ACL_A_EMAIL', 'cat' => 'misc'), + 'acl_a_bots' => array('lang' => 'ACL_A_BOTS', 'cat' => 'misc'), + 'acl_a_reasons' => array('lang' => 'ACL_A_REASONS', 'cat' => 'misc'), + 'acl_a_backup' => array('lang' => 'ACL_A_BACKUP', 'cat' => 'misc'), + 'acl_a_search' => array('lang' => 'ACL_A_SEARCH', 'cat' => 'misc'), ); } diff --git a/phpBB/language/en/acp/permissions_phpbb.php b/phpBB/language/en/acp/permissions_phpbb.php index 4f2c9067d2..edcc812830 100644 --- a/phpBB/language/en/acp/permissions_phpbb.php +++ b/phpBB/language/en/acp/permissions_phpbb.php @@ -56,8 +56,8 @@ if (empty($lang) || !is_array($lang)) * * // Adding the permissions * $lang = array_merge($lang, array( -* 'acl_bug_view' => array('lang' => 'Can view bug reports', 'cat' => 'bugs'), -* 'acl_bug_post' => array('lang' => 'Can post bugs', 'cat' => 'post'), // Using a phpBB category here +* 'acl_bug_view' => 'Can view bug reports', 'cat' => 'bugs'), +* 'acl_bug_post' => 'Can post bugs', // Using a phpBB category here * )); * * @@ -82,146 +82,135 @@ $lang = array_merge($lang, array( // User Permissions $lang = array_merge($lang, array( - 'acl_u_viewprofile' => array('lang' => 'Can view profiles, memberlist and online list', 'cat' => 'profile'), - 'acl_u_chgname' => array('lang' => 'Can change username', 'cat' => 'profile'), - 'acl_u_chgpasswd' => array('lang' => 'Can change password', 'cat' => 'profile'), - 'acl_u_chgemail' => array('lang' => 'Can change email address', 'cat' => 'profile'), - 'acl_u_chgavatar' => array('lang' => 'Can change avatar', 'cat' => 'profile'), - 'acl_u_chggrp' => array('lang' => 'Can change default usergroup', 'cat' => 'profile'), - 'acl_u_chgprofileinfo' => array('lang' => 'Can change profile field information', 'cat' => 'profile'), - - 'acl_u_attach' => array('lang' => 'Can attach files', 'cat' => 'post'), - 'acl_u_download' => array('lang' => 'Can download files', 'cat' => 'post'), - 'acl_u_savedrafts' => array('lang' => 'Can save drafts', 'cat' => 'post'), - 'acl_u_chgcensors' => array('lang' => 'Can disable word censors', 'cat' => 'post'), - 'acl_u_sig' => array('lang' => 'Can use signature', 'cat' => 'post'), - - 'acl_u_sendpm' => array('lang' => 'Can send private messages', 'cat' => 'pm'), - 'acl_u_masspm' => array('lang' => 'Can send messages to multiple users', 'cat' => 'pm'), - 'acl_u_masspm_group'=> array('lang' => 'Can send messages to groups', 'cat' => 'pm'), - 'acl_u_readpm' => array('lang' => 'Can read private messages', 'cat' => 'pm'), - 'acl_u_pm_edit' => array('lang' => 'Can edit own private messages', 'cat' => 'pm'), - 'acl_u_pm_delete' => array('lang' => 'Can remove private messages from own folder', 'cat' => 'pm'), - 'acl_u_pm_forward' => array('lang' => 'Can forward private messages', 'cat' => 'pm'), - 'acl_u_pm_emailpm' => array('lang' => 'Can email private messages', 'cat' => 'pm'), - 'acl_u_pm_printpm' => array('lang' => 'Can print private messages', 'cat' => 'pm'), - 'acl_u_pm_attach' => array('lang' => 'Can attach files in private messages', 'cat' => 'pm'), - 'acl_u_pm_download' => array('lang' => 'Can download files in private messages', 'cat' => 'pm'), - 'acl_u_pm_bbcode' => array('lang' => 'Can use BBCode in private messages', 'cat' => 'pm'), - 'acl_u_pm_smilies' => array('lang' => 'Can use smilies in private messages', 'cat' => 'pm'), - 'acl_u_pm_img' => array('lang' => 'Can use [img] BBCode tag in private messages', 'cat' => 'pm'), - 'acl_u_pm_flash' => array('lang' => 'Can use [flash] BBCode tag in private messages', 'cat' => 'pm'), - - 'acl_u_sendemail' => array('lang' => 'Can send emails', 'cat' => 'misc'), - 'acl_u_sendim' => array('lang' => 'Can send instant messages', 'cat' => 'misc'), - 'acl_u_ignoreflood' => array('lang' => 'Can ignore flood limit', 'cat' => 'misc'), - 'acl_u_hideonline' => array('lang' => 'Can hide online status', 'cat' => 'misc'), - 'acl_u_viewonline' => array('lang' => 'Can view hidden online users', 'cat' => 'misc'), - 'acl_u_search' => array('lang' => 'Can search board', 'cat' => 'misc'), + 'ACL_U_VIEWPROFILE' => 'Can view profiles, memberlist and online list', + 'ACL_U_CHGNAME' => 'Can change username', + 'ACL_U_CHGPASSWD' => 'Can change password', + 'ACL_U_CHGEMAIL' => 'Can change email address', + 'ACL_U_CHGAVATAR' => 'Can change avatar', + 'ACL_U_CHGGRP' => 'Can change default usergroup', + 'ACL_U_CHGPROFILEINFO' => 'Can change profile field information', + + 'ACL_U_ATTACH' => 'Can attach files', + 'ACL_U_DOWNLOAD' => 'Can download files', + 'ACL_U_SAVEDRAFTS' => 'Can save drafts', + 'ACL_U_CHGCENSORS' => 'Can disable word censors', + 'ACL_U_SIG' => 'Can use signature', + + 'ACL_U_SENDPM' => 'Can send private messages', + 'ACL_U_MASSPM' => 'Can send messages to multiple users', + 'ACL_U_MASSPM_GROUP'=> 'Can send messages to groups', + 'ACL_U_READPM' => 'Can read private messages', + 'ACL_U_PM_EDIT' => 'Can edit own private messages', + 'ACL_U_PM_DELETE' => 'Can remove private messages from own folder', + 'ACL_U_PM_FORWARD' => 'Can forward private messages', + 'ACL_U_PM_EMAILPM' => 'Can email private messages', + 'ACL_U_PM_PRINTPM' => 'Can print private messages', + 'ACL_U_PM_ATTACH' => 'Can attach files in private messages', + 'ACL_U_PM_DOWNLOAD' => 'Can download files in private messages', + 'ACL_U_PM_BBCODE' => 'Can use BBCode in private messages', + 'ACL_U_PM_SMILIES' => 'Can use smilies in private messages', + 'ACL_U_PM_IMG' => 'Can use [img] BBCode tag in private messages', + 'ACL_U_PM_FLASH' => 'Can use [flash] BBCode tag in private messages', + + 'ACL_U_SENDEMAIL' => 'Can send emails', + 'ACL_U_SENDIM' => 'Can send instant messages', + 'ACL_U_IGNOREFLOOD' => 'Can ignore flood limit', + 'ACL_U_HIDEONLINE' => 'Can hide online status', + 'ACL_U_VIEWONLINE' => 'Can view hidden online users', + 'ACL_U_SEARCH' => 'Can search board', )); // Forum Permissions $lang = array_merge($lang, array( - 'acl_f_list' => array('lang' => 'Can see forum', 'cat' => 'actions'), - 'acl_f_read' => array('lang' => 'Can read forum', 'cat' => 'actions'), - 'acl_f_search' => array('lang' => 'Can search the forum', 'cat' => 'actions'), - 'acl_f_subscribe' => array('lang' => 'Can subscribe forum', 'cat' => 'actions'), - 'acl_f_print' => array('lang' => 'Can print topics', 'cat' => 'actions'), - 'acl_f_email' => array('lang' => 'Can email topics', 'cat' => 'actions'), - 'acl_f_bump' => array('lang' => 'Can bump topics', 'cat' => 'actions'), - 'acl_f_user_lock' => array('lang' => 'Can lock own topics', 'cat' => 'actions'), - 'acl_f_download' => array('lang' => 'Can download files', 'cat' => 'actions'), - 'acl_f_report' => array('lang' => 'Can report posts', 'cat' => 'actions'), - - 'acl_f_post' => array('lang' => 'Can start new topics', 'cat' => 'post'), - 'acl_f_sticky' => array('lang' => 'Can post stickies', 'cat' => 'post'), - 'acl_f_announce' => array('lang' => 'Can post announcements', 'cat' => 'post'), - 'acl_f_reply' => array('lang' => 'Can reply to topics', 'cat' => 'post'), - 'acl_f_edit' => array('lang' => 'Can edit own posts', 'cat' => 'post'), - 'acl_f_delete' => array('lang' => 'Can permanently delete own posts', 'cat' => 'post'), - 'acl_f_softdelete' => array('lang' => 'Can soft delete own posts
Moderators, who have the approve posts permission, can restore soft deleted posts.', 'cat' => 'post'), - 'acl_f_ignoreflood' => array('lang' => 'Can ignore flood limit', 'cat' => 'post'), - 'acl_f_postcount' => array('lang' => 'Increment post counter
Please note that this setting only affects new posts.', 'cat' => 'post'), - 'acl_f_noapprove' => array('lang' => 'Can post without approval', 'cat' => 'post'), - - 'acl_f_attach' => array('lang' => 'Can attach files', 'cat' => 'content'), - 'acl_f_icons' => array('lang' => 'Can use topic/post icons', 'cat' => 'content'), - 'acl_f_bbcode' => array('lang' => 'Can use BBCode', 'cat' => 'content'), - 'acl_f_flash' => array('lang' => 'Can use [flash] BBCode tag', 'cat' => 'content'), - 'acl_f_img' => array('lang' => 'Can use [img] BBCode tag', 'cat' => 'content'), - 'acl_f_sigs' => array('lang' => 'Can use signatures', 'cat' => 'content'), - 'acl_f_smilies' => array('lang' => 'Can use smilies', 'cat' => 'content'), - - 'acl_f_poll' => array('lang' => 'Can create polls', 'cat' => 'polls'), - 'acl_f_vote' => array('lang' => 'Can vote in polls', 'cat' => 'polls'), - 'acl_f_votechg' => array('lang' => 'Can change existing vote', 'cat' => 'polls'), + 'ACL_F_POST' => 'Can start new topics', + 'ACL_F_STICKY' => 'Can post stickies', + 'ACL_F_ANNOUNCE' => 'Can post announcements', + 'ACL_F_REPLY' => 'Can reply to topics', + 'ACL_F_EDIT' => 'Can edit own posts', + 'ACL_F_DELETE' => 'Can permanently delete own posts', + 'ACL_F_SOFTDELETE' => 'Can soft delete own posts
Moderators, who have the approve posts permission, can restore soft deleted posts.', + 'ACL_F_IGNOREFLOOD' => 'Can ignore flood limit', + 'ACL_F_POSTCOUNT' => 'Increment post counter
Please note that this setting only affects new posts.', + 'ACL_F_NOAPPROVE' => 'Can post without approval', + + 'ACL_F_ATTACH' => 'Can attach files', + 'ACL_F_ICONS' => 'Can use topic/post icons', + 'ACL_F_BBCODE' => 'Can use BBCode', + 'ACL_F_FLASH' => 'Can use [flash] BBCode tag', + 'ACL_F_IMG' => 'Can use [img] BBCode tag', + 'ACL_F_SIGS' => 'Can use signatures', + 'ACL_F_SMILIES' => 'Can use smilies', + + 'ACL_F_POLL' => 'Can create polls', + 'ACL_F_VOTE' => 'Can vote in polls', + 'ACL_F_VOTECHG' => 'Can change existing vote', )); // Moderator Permissions $lang = array_merge($lang, array( - 'acl_m_edit' => array('lang' => 'Can edit posts', 'cat' => 'post_actions'), - 'acl_m_delete' => array('lang' => 'Can permanently delete posts', 'cat' => 'post_actions'), - 'acl_m_softdelete' => array('lang' => 'Can soft delete posts
Moderators, who have the approve posts permission, can restore soft deleted posts.', 'cat' => 'post_actions'), - 'acl_m_approve' => array('lang' => 'Can approve and restore posts', 'cat' => 'post_actions'), - 'acl_m_report' => array('lang' => 'Can close and delete reports', 'cat' => 'post_actions'), - 'acl_m_chgposter' => array('lang' => 'Can change post author', 'cat' => 'post_actions'), - - 'acl_m_move' => array('lang' => 'Can move topics', 'cat' => 'topic_actions'), - 'acl_m_lock' => array('lang' => 'Can lock topics', 'cat' => 'topic_actions'), - 'acl_m_split' => array('lang' => 'Can split topics', 'cat' => 'topic_actions'), - 'acl_m_merge' => array('lang' => 'Can merge topics', 'cat' => 'topic_actions'), - - 'acl_m_info' => array('lang' => 'Can view post details', 'cat' => 'misc'), - 'acl_m_warn' => array('lang' => 'Can issue warnings
This setting is only assigned globally. It is not forum based.', 'cat' => 'misc'), // This moderator setting is only global (and not local) - 'acl_m_ban' => array('lang' => 'Can manage bans
This setting is only assigned globally. It is not forum based.', 'cat' => 'misc'), // This moderator setting is only global (and not local) + 'ACL_M_EDIT' => 'Can edit posts', + 'ACL_M_DELETE' => 'Can permanently delete posts', + 'ACL_M_SOFTDELETE' => 'Can soft delete posts
Moderators, who have the approve posts permission, can restore soft deleted posts.', + 'ACL_M_APPROVE' => 'Can approve posts', + 'ACL_M_REPORT' => 'Can close and delete reports', + 'ACL_M_CHGPOSTER' => 'Can change post author', + + 'ACL_M_MOVE' => 'Can move topics', + 'ACL_M_LOCK' => 'Can lock topics', + 'ACL_M_SPLIT' => 'Can split topics', + 'ACL_M_MERGE' => 'Can merge topics', + + 'ACL_M_INFO' => 'Can view post details', + 'ACL_M_WARN' => 'Can issue warnings
This setting is only assigned globally. It is not forum based.', // This moderator setting is only global (and not local) + 'ACL_M_BAN' => 'Can manage bans
This setting is only assigned globally. It is not forum based.', // This moderator setting is only global (and not local) )); // Admin Permissions $lang = array_merge($lang, array( - 'acl_a_board' => array('lang' => 'Can alter board settings/check for updates', 'cat' => 'settings'), - 'acl_a_server' => array('lang' => 'Can alter server/communication settings', 'cat' => 'settings'), - 'acl_a_jabber' => array('lang' => 'Can alter Jabber settings', 'cat' => 'settings'), - 'acl_a_phpinfo' => array('lang' => 'Can view php settings', 'cat' => 'settings'), - - 'acl_a_forum' => array('lang' => 'Can manage forums', 'cat' => 'forums'), - 'acl_a_forumadd' => array('lang' => 'Can add new forums', 'cat' => 'forums'), - 'acl_a_forumdel' => array('lang' => 'Can delete forums', 'cat' => 'forums'), - 'acl_a_prune' => array('lang' => 'Can prune forums', 'cat' => 'forums'), - - 'acl_a_icons' => array('lang' => 'Can alter topic/post icons and smilies', 'cat' => 'posting'), - 'acl_a_words' => array('lang' => 'Can alter word censors', 'cat' => 'posting'), - 'acl_a_bbcode' => array('lang' => 'Can define BBCode tags', 'cat' => 'posting'), - 'acl_a_attach' => array('lang' => 'Can alter attachment related settings', 'cat' => 'posting'), - - 'acl_a_user' => array('lang' => 'Can manage users
This also includes seeing the users browser agent within the viewonline list.', 'cat' => 'user_group'), - 'acl_a_userdel' => array('lang' => 'Can delete/prune users', 'cat' => 'user_group'), - 'acl_a_group' => array('lang' => 'Can manage groups', 'cat' => 'user_group'), - 'acl_a_groupadd' => array('lang' => 'Can add new groups', 'cat' => 'user_group'), - 'acl_a_groupdel' => array('lang' => 'Can delete groups', 'cat' => 'user_group'), - 'acl_a_ranks' => array('lang' => 'Can manage ranks', 'cat' => 'user_group'), - 'acl_a_profile' => array('lang' => 'Can manage custom profile fields', 'cat' => 'user_group'), - 'acl_a_names' => array('lang' => 'Can manage disallowed names', 'cat' => 'user_group'), - 'acl_a_ban' => array('lang' => 'Can manage bans', 'cat' => 'user_group'), - - 'acl_a_viewauth' => array('lang' => 'Can view permission masks', 'cat' => 'permissions'), - 'acl_a_authgroups' => array('lang' => 'Can alter permissions for individual groups', 'cat' => 'permissions'), - 'acl_a_authusers' => array('lang' => 'Can alter permissions for individual users', 'cat' => 'permissions'), - 'acl_a_fauth' => array('lang' => 'Can alter forum permission class', 'cat' => 'permissions'), - 'acl_a_mauth' => array('lang' => 'Can alter moderator permission class', 'cat' => 'permissions'), - 'acl_a_aauth' => array('lang' => 'Can alter admin permission class', 'cat' => 'permissions'), - 'acl_a_uauth' => array('lang' => 'Can alter user permission class', 'cat' => 'permissions'), - 'acl_a_roles' => array('lang' => 'Can manage roles', 'cat' => 'permissions'), - 'acl_a_switchperm' => array('lang' => 'Can use others permissions', 'cat' => 'permissions'), - - 'acl_a_styles' => array('lang' => 'Can manage styles', 'cat' => 'misc'), - 'acl_a_extensions' => array('lang' => 'Can manage extensions', 'cat' => 'misc'), - 'acl_a_viewlogs' => array('lang' => 'Can view logs', 'cat' => 'misc'), - 'acl_a_clearlogs' => array('lang' => 'Can clear logs', 'cat' => 'misc'), - 'acl_a_modules' => array('lang' => 'Can manage modules', 'cat' => 'misc'), - 'acl_a_language' => array('lang' => 'Can manage language packs', 'cat' => 'misc'), - 'acl_a_email' => array('lang' => 'Can send mass email', 'cat' => 'misc'), - 'acl_a_bots' => array('lang' => 'Can manage bots', 'cat' => 'misc'), - 'acl_a_reasons' => array('lang' => 'Can manage report/denial reasons', 'cat' => 'misc'), - 'acl_a_backup' => array('lang' => 'Can backup/restore database', 'cat' => 'misc'), - 'acl_a_search' => array('lang' => 'Can manage search backends and settings', 'cat' => 'misc'), + 'ACL_A_BOARD' => 'Can alter board settings/check for updates', + 'ACL_A_SERVER' => 'Can alter server/communication settings', + 'ACL_A_JABBER' => 'Can alter Jabber settings', + 'ACL_A_PHPINFO' => 'Can view php settings', + + 'ACL_A_FORUM' => 'Can manage forums', + 'ACL_A_FORUMADD' => 'Can add new forums', + 'ACL_A_FORUMDEL' => 'Can delete forums', + 'ACL_A_PRUNE' => 'Can prune forums', + + 'ACL_A_ICONS' => 'Can alter topic/post icons and smilies', + 'ACL_A_WORDS' => 'Can alter word censors', + 'ACL_A_BBCODE' => 'Can define BBCode tags', + 'ACL_A_ATTACH' => 'Can alter attachment related settings', + + 'ACL_A_USER' => 'Can manage users
This also includes seeing the users browser agent within the viewonline list.', + 'ACL_A_USERDEL' => 'Can delete/prune users', + 'ACL_A_GROUP' => 'Can manage groups', + 'ACL_A_GROUPADD' => 'Can add new groups', + 'ACL_A_GROUPDEL' => 'Can delete groups', + 'ACL_A_RANKS' => 'Can manage ranks', + 'ACL_A_PROFILE' => 'Can manage custom profile fields', + 'ACL_A_NAMES' => 'Can manage disallowed names', + 'ACL_A_BAN' => 'Can manage bans', + + 'ACL_A_VIEWAUTH' => 'Can view permission masks', + 'ACL_A_AUTHGROUPS' => 'Can alter permissions for individual groups', + 'ACL_A_AUTHUSERS' => 'Can alter permissions for individual users', + 'ACL_A_FAUTH' => 'Can alter forum permission class', + 'ACL_A_MAUTH' => 'Can alter moderator permission class', + 'ACL_A_AAUTH' => 'Can alter admin permission class', + 'ACL_A_UAUTH' => 'Can alter user permission class', + 'ACL_A_ROLES' => 'Can manage roles', + 'ACL_A_SWITCHPERM' => 'Can use others permissions', + + 'ACL_A_STYLES' => 'Can manage styles', + 'ACL_A_EXTENSIONS' => 'Can manage extensions', + 'ACL_A_VIEWLOGS' => 'Can view logs', + 'ACL_A_CLEARLOGS' => 'Can clear logs', + 'ACL_A_MODULES' => 'Can manage modules', + 'ACL_A_LANGUAGE' => 'Can manage language packs', + 'ACL_A_EMAIL' => 'Can send mass email', + 'ACL_A_BOTS' => 'Can manage bots', + 'ACL_A_REASONS' => 'Can manage report/denial reasons', + 'ACL_A_BACKUP' => 'Can backup/restore database', + 'ACL_A_SEARCH' => 'Can manage search backends and settings', )); -- cgit v1.2.1 From 4a64e2c2b3d4b39cdedec72551076595a9cd6a6e Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Sat, 6 Jul 2013 16:07:05 +0200 Subject: [ticket/11582] Fix documentation for adding permissions PHPBB3-11582 --- phpBB/language/en/acp/permissions_phpbb.php | 34 ++++++----------------------- 1 file changed, 7 insertions(+), 27 deletions(-) (limited to 'phpBB') diff --git a/phpBB/language/en/acp/permissions_phpbb.php b/phpBB/language/en/acp/permissions_phpbb.php index edcc812830..d0128db34a 100644 --- a/phpBB/language/en/acp/permissions_phpbb.php +++ b/phpBB/language/en/acp/permissions_phpbb.php @@ -33,34 +33,14 @@ if (empty($lang) || !is_array($lang)) // in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine /** -* MODDERS PLEASE NOTE +* EXTENSION-DEVELOPERS PLEASE NOTE * -* You are able to put your permission sets into a separate file too by -* prefixing the new file with permissions_ and putting it into the acp -* language folder. -* -* An example of how the file could look like: -* -* -* -* if (empty($lang) || !is_array($lang)) -* { -* $lang = array(); -* } -* -* // Adding new category -* $lang['permission_cat']['bugs'] = 'Bugs'; -* -* // Adding new permission set -* $lang['permission_type']['bug_'] = 'Bug Permissions'; -* -* // Adding the permissions -* $lang = array_merge($lang, array( -* 'acl_bug_view' => 'Can view bug reports', 'cat' => 'bugs'), -* 'acl_bug_post' => 'Can post bugs', // Using a phpBB category here -* )); -* -* +* You are able to put your permission sets into your extension. +* The permissions logic should be added via the 'core.permissions' event. +* You can easily add new permission categories, types and permissions, by +* simply merging them into the respective arrays. +* The respective language strings should be added into a language file, that +* start with 'permissions_', so they are automatically loaded within the ACP. */ $lang = array_merge($lang, array( -- cgit v1.2.1 From aaa44eda2b1df7d7c5c02651c1a9536343eca846 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Mon, 8 Jul 2013 00:48:26 +0200 Subject: [ticket/11582] Remove useless prefix PHPBB3-11582 --- phpBB/includes/permissions.php | 266 ++++++++++++++++++++--------------------- 1 file changed, 133 insertions(+), 133 deletions(-) (limited to 'phpBB') diff --git a/phpBB/includes/permissions.php b/phpBB/includes/permissions.php index 4cb3356c53..1db6843dbb 100644 --- a/phpBB/includes/permissions.php +++ b/phpBB/includes/permissions.php @@ -52,14 +52,14 @@ class phpbb_permissions * @var array types Array with permission types (a_, u_, m_, etc.) * @var array categories Array with permission categories (pm, post, settings, misc, etc.) * @var array permissions Array with permissions. Each Permission has the following layout: - * 'acl_' => array( + * '' => array( * 'lang' => 'Language Key with a Short description', // Optional, if not set, - * // the permissions identifier 'acl_' is used with + * // the permissions identifier '' is used with * // all uppercase. * 'cat' => 'Identifier of the category, the permission should be displayed in', * ), * Example: - * 'acl_u_viewprofile' => array( + * 'u_viewprofile' => array( * 'lang' => 'ACL_U_VIEWPROFILE', * 'cat' => 'profile', * ), @@ -129,14 +129,14 @@ class phpbb_permissions /** * Returns an array with all the permissions. * Each Permission has the following layout: - * 'acl_' => array( + * '' => array( * 'lang' => 'Language Key with a Short description', // Optional, if not set, - * // the permissions identifier 'acl_' is used with + * // the permissions identifier '' is used with * // all uppercase. * 'cat' => 'Identifier of the category, the permission should be displayed in', * ), * Example: - * 'acl_u_viewprofile' => array( + * 'u_viewprofile' => array( * 'lang' => 'ACL_U_VIEWPROFILE', * 'cat' => 'profile', * ), @@ -165,7 +165,7 @@ class phpbb_permissions */ public function get_permission_lang($permission) { - return (isset($this->permissions['acl_' . $permission]['lang'])) ? $this->user->lang($this->permissions['acl_' . $permission]['lang']) : $this->user->lang('ACL_' . strtoupper($permission)); + return (isset($this->permissions[$permission]['lang'])) ? $this->user->lang($this->permissions[$permission]['lang']) : $this->user->lang('ACL_' . strtoupper($permission)); } protected $types = array( @@ -197,139 +197,139 @@ class phpbb_permissions protected $permissions = array( // User Permissions - 'acl_u_viewprofile' => array('lang' => 'ACL_U_VIEWPROFILE', 'cat' => 'profile'), - 'acl_u_chgname' => array('lang' => 'ACL_U_CHGNAME', 'cat' => 'profile'), - 'acl_u_chgpasswd' => array('lang' => 'ACL_U_CHGPASSWD', 'cat' => 'profile'), - 'acl_u_chgemail' => array('lang' => 'ACL_U_CHGEMAIL', 'cat' => 'profile'), - 'acl_u_chgavatar' => array('lang' => 'ACL_U_CHGAVATAR', 'cat' => 'profile'), - 'acl_u_chggrp' => array('lang' => 'ACL_U_CHGGRP', 'cat' => 'profile'), - 'acl_u_chgprofileinfo' => array('lang' => 'ACL_U_CHGPROFILEINFO', 'cat' => 'profile'), - - 'acl_u_attach' => array('lang' => 'ACL_U_ATTACH', 'cat' => 'post'), - 'acl_u_download' => array('lang' => 'ACL_U_DOWNLOAD', 'cat' => 'post'), - 'acl_u_savedrafts' => array('lang' => 'ACL_U_SAVEDRAFTS', 'cat' => 'post'), - 'acl_u_chgcensors' => array('lang' => 'ACL_U_CHGCENSORS', 'cat' => 'post'), - 'acl_u_sig' => array('lang' => 'ACL_U_SIG', 'cat' => 'post'), - - 'acl_u_sendpm' => array('lang' => 'ACL_U_SENDPM', 'cat' => 'pm'), - 'acl_u_masspm' => array('lang' => 'ACL_U_MASSPM', 'cat' => 'pm'), - 'acl_u_masspm_group'=> array('lang' => 'ACL_U_MASSPM_GROUP', 'cat' => 'pm'), - 'acl_u_readpm' => array('lang' => 'ACL_U_READPM', 'cat' => 'pm'), - 'acl_u_pm_edit' => array('lang' => 'ACL_U_PM_EDIT', 'cat' => 'pm'), - 'acl_u_pm_delete' => array('lang' => 'ACL_U_PM_DELETE', 'cat' => 'pm'), - 'acl_u_pm_forward' => array('lang' => 'ACL_U_PM_FORWARD', 'cat' => 'pm'), - 'acl_u_pm_emailpm' => array('lang' => 'ACL_U_PM_EMAILPM', 'cat' => 'pm'), - 'acl_u_pm_printpm' => array('lang' => 'ACL_U_PM_PRINTPM', 'cat' => 'pm'), - 'acl_u_pm_attach' => array('lang' => 'ACL_U_PM_ATTACH', 'cat' => 'pm'), - 'acl_u_pm_download' => array('lang' => 'ACL_U_PM_DOWNLOAD', 'cat' => 'pm'), - 'acl_u_pm_bbcode' => array('lang' => 'ACL_U_PM_BBCODE', 'cat' => 'pm'), - 'acl_u_pm_smilies' => array('lang' => 'ACL_U_PM_SMILIES', 'cat' => 'pm'), - 'acl_u_pm_img' => array('lang' => 'ACL_U_PM_IMG', 'cat' => 'pm'), - 'acl_u_pm_flash' => array('lang' => 'ACL_U_PM_FLASH', 'cat' => 'pm'), - - 'acl_u_sendemail' => array('lang' => 'ACL_U_SENDEMAIL', 'cat' => 'misc'), - 'acl_u_sendim' => array('lang' => 'ACL_U_SENDIM', 'cat' => 'misc'), - 'acl_u_ignoreflood' => array('lang' => 'ACL_U_IGNOREFLOOD', 'cat' => 'misc'), - 'acl_u_hideonline' => array('lang' => 'ACL_U_HIDEONLINE', 'cat' => 'misc'), - 'acl_u_viewonline' => array('lang' => 'ACL_U_VIEWONLINE', 'cat' => 'misc'), - 'acl_u_search' => array('lang' => 'ACL_U_SEARCH', 'cat' => 'misc'), + 'u_viewprofile' => array('lang' => 'ACL_U_VIEWPROFILE', 'cat' => 'profile'), + 'u_chgname' => array('lang' => 'ACL_U_CHGNAME', 'cat' => 'profile'), + 'u_chgpasswd' => array('lang' => 'ACL_U_CHGPASSWD', 'cat' => 'profile'), + 'u_chgemail' => array('lang' => 'ACL_U_CHGEMAIL', 'cat' => 'profile'), + 'u_chgavatar' => array('lang' => 'ACL_U_CHGAVATAR', 'cat' => 'profile'), + 'u_chggrp' => array('lang' => 'ACL_U_CHGGRP', 'cat' => 'profile'), + 'u_chgprofileinfo' => array('lang' => 'ACL_U_CHGPROFILEINFO', 'cat' => 'profile'), + + 'u_attach' => array('lang' => 'ACL_U_ATTACH', 'cat' => 'post'), + 'u_download' => array('lang' => 'ACL_U_DOWNLOAD', 'cat' => 'post'), + 'u_savedrafts' => array('lang' => 'ACL_U_SAVEDRAFTS', 'cat' => 'post'), + 'u_chgcensors' => array('lang' => 'ACL_U_CHGCENSORS', 'cat' => 'post'), + 'u_sig' => array('lang' => 'ACL_U_SIG', 'cat' => 'post'), + + 'u_sendpm' => array('lang' => 'ACL_U_SENDPM', 'cat' => 'pm'), + 'u_masspm' => array('lang' => 'ACL_U_MASSPM', 'cat' => 'pm'), + 'u_masspm_group'=> array('lang' => 'ACL_U_MASSPM_GROUP', 'cat' => 'pm'), + 'u_readpm' => array('lang' => 'ACL_U_READPM', 'cat' => 'pm'), + 'u_pm_edit' => array('lang' => 'ACL_U_PM_EDIT', 'cat' => 'pm'), + 'u_pm_delete' => array('lang' => 'ACL_U_PM_DELETE', 'cat' => 'pm'), + 'u_pm_forward' => array('lang' => 'ACL_U_PM_FORWARD', 'cat' => 'pm'), + 'u_pm_emailpm' => array('lang' => 'ACL_U_PM_EMAILPM', 'cat' => 'pm'), + 'u_pm_printpm' => array('lang' => 'ACL_U_PM_PRINTPM', 'cat' => 'pm'), + 'u_pm_attach' => array('lang' => 'ACL_U_PM_ATTACH', 'cat' => 'pm'), + 'u_pm_download' => array('lang' => 'ACL_U_PM_DOWNLOAD', 'cat' => 'pm'), + 'u_pm_bbcode' => array('lang' => 'ACL_U_PM_BBCODE', 'cat' => 'pm'), + 'u_pm_smilies' => array('lang' => 'ACL_U_PM_SMILIES', 'cat' => 'pm'), + 'u_pm_img' => array('lang' => 'ACL_U_PM_IMG', 'cat' => 'pm'), + 'u_pm_flash' => array('lang' => 'ACL_U_PM_FLASH', 'cat' => 'pm'), + + 'u_sendemail' => array('lang' => 'ACL_U_SENDEMAIL', 'cat' => 'misc'), + 'u_sendim' => array('lang' => 'ACL_U_SENDIM', 'cat' => 'misc'), + 'u_ignoreflood' => array('lang' => 'ACL_U_IGNOREFLOOD', 'cat' => 'misc'), + 'u_hideonline' => array('lang' => 'ACL_U_HIDEONLINE', 'cat' => 'misc'), + 'u_viewonline' => array('lang' => 'ACL_U_VIEWONLINE', 'cat' => 'misc'), + 'u_search' => array('lang' => 'ACL_U_SEARCH', 'cat' => 'misc'), // Forum Permissions - 'acl_f_list' => array('lang' => 'ACL_F_LIST', 'cat' => 'actions'), - 'acl_f_read' => array('lang' => 'ACL_F_READ', 'cat' => 'actions'), - 'acl_f_search' => array('lang' => 'ACL_F_SEARCH', 'cat' => 'actions'), - 'acl_f_subscribe' => array('lang' => 'ACL_F_SUBSCRIBE', 'cat' => 'actions'), - 'acl_f_print' => array('lang' => 'ACL_F_PRINT', 'cat' => 'actions'), - 'acl_f_email' => array('lang' => 'ACL_F_EMAIL', 'cat' => 'actions'), - 'acl_f_bump' => array('lang' => 'ACL_F_BUMP', 'cat' => 'actions'), - 'acl_f_user_lock' => array('lang' => 'ACL_F_USER_LOCK', 'cat' => 'actions'), - 'acl_f_download' => array('lang' => 'ACL_F_DOWNLOAD', 'cat' => 'actions'), - 'acl_f_report' => array('lang' => 'ACL_F_REPORT', 'cat' => 'actions'), - - 'acl_f_post' => array('lang' => 'ACL_F_POST', 'cat' => 'post'), - 'acl_f_sticky' => array('lang' => 'ACL_F_STICKY', 'cat' => 'post'), - 'acl_f_announce' => array('lang' => 'ACL_F_ANNOUNCE', 'cat' => 'post'), - 'acl_f_reply' => array('lang' => 'ACL_F_REPLY', 'cat' => 'post'), - 'acl_f_edit' => array('lang' => 'ACL_F_EDIT', 'cat' => 'post'), - 'acl_f_delete' => array('lang' => 'ACL_F_DELETE', 'cat' => 'post'), - 'acl_f_ignoreflood' => array('lang' => 'ACL_F_IGNOREFLOOD', 'cat' => 'post'), - 'acl_f_postcount' => array('lang' => 'ACL_F_POSTCOUNT', 'cat' => 'post'), - 'acl_f_noapprove' => array('lang' => 'ACL_F_NOAPPROVE', 'cat' => 'post'), - - 'acl_f_attach' => array('lang' => 'ACL_F_ATTACH', 'cat' => 'content'), - 'acl_f_icons' => array('lang' => 'ACL_F_ICONS', 'cat' => 'content'), - 'acl_f_bbcode' => array('lang' => 'ACL_F_BBCODE', 'cat' => 'content'), - 'acl_f_flash' => array('lang' => 'ACL_F_FLASH', 'cat' => 'content'), - 'acl_f_img' => array('lang' => 'ACL_F_IMG', 'cat' => 'content'), - 'acl_f_sigs' => array('lang' => 'ACL_F_SIGS', 'cat' => 'content'), - 'acl_f_smilies' => array('lang' => 'ACL_F_SMILIES', 'cat' => 'content'), - - 'acl_f_poll' => array('lang' => 'ACL_F_POLL', 'cat' => 'polls'), - 'acl_f_vote' => array('lang' => 'ACL_F_VOTE', 'cat' => 'polls'), - 'acl_f_votechg' => array('lang' => 'ACL_F_VOTECHG', 'cat' => 'polls'), + 'f_list' => array('lang' => 'ACL_F_LIST', 'cat' => 'actions'), + 'f_read' => array('lang' => 'ACL_F_READ', 'cat' => 'actions'), + 'f_search' => array('lang' => 'ACL_F_SEARCH', 'cat' => 'actions'), + 'f_subscribe' => array('lang' => 'ACL_F_SUBSCRIBE', 'cat' => 'actions'), + 'f_print' => array('lang' => 'ACL_F_PRINT', 'cat' => 'actions'), + 'f_email' => array('lang' => 'ACL_F_EMAIL', 'cat' => 'actions'), + 'f_bump' => array('lang' => 'ACL_F_BUMP', 'cat' => 'actions'), + 'f_user_lock' => array('lang' => 'ACL_F_USER_LOCK', 'cat' => 'actions'), + 'f_download' => array('lang' => 'ACL_F_DOWNLOAD', 'cat' => 'actions'), + 'f_report' => array('lang' => 'ACL_F_REPORT', 'cat' => 'actions'), + + 'f_post' => array('lang' => 'ACL_F_POST', 'cat' => 'post'), + 'f_sticky' => array('lang' => 'ACL_F_STICKY', 'cat' => 'post'), + 'f_announce' => array('lang' => 'ACL_F_ANNOUNCE', 'cat' => 'post'), + 'f_reply' => array('lang' => 'ACL_F_REPLY', 'cat' => 'post'), + 'f_edit' => array('lang' => 'ACL_F_EDIT', 'cat' => 'post'), + 'f_delete' => array('lang' => 'ACL_F_DELETE', 'cat' => 'post'), + 'f_ignoreflood' => array('lang' => 'ACL_F_IGNOREFLOOD', 'cat' => 'post'), + 'f_postcount' => array('lang' => 'ACL_F_POSTCOUNT', 'cat' => 'post'), + 'f_noapprove' => array('lang' => 'ACL_F_NOAPPROVE', 'cat' => 'post'), + + 'f_attach' => array('lang' => 'ACL_F_ATTACH', 'cat' => 'content'), + 'f_icons' => array('lang' => 'ACL_F_ICONS', 'cat' => 'content'), + 'f_bbcode' => array('lang' => 'ACL_F_BBCODE', 'cat' => 'content'), + 'f_flash' => array('lang' => 'ACL_F_FLASH', 'cat' => 'content'), + 'f_img' => array('lang' => 'ACL_F_IMG', 'cat' => 'content'), + 'f_sigs' => array('lang' => 'ACL_F_SIGS', 'cat' => 'content'), + 'f_smilies' => array('lang' => 'ACL_F_SMILIES', 'cat' => 'content'), + + 'f_poll' => array('lang' => 'ACL_F_POLL', 'cat' => 'polls'), + 'f_vote' => array('lang' => 'ACL_F_VOTE', 'cat' => 'polls'), + 'f_votechg' => array('lang' => 'ACL_F_VOTECHG', 'cat' => 'polls'), // Moderator Permissions - 'acl_m_edit' => array('lang' => 'ACL_M_EDIT', 'cat' => 'post_actions'), - 'acl_m_delete' => array('lang' => 'ACL_M_DELETE', 'cat' => 'post_actions'), - 'acl_m_approve' => array('lang' => 'ACL_M_APPROVE', 'cat' => 'post_actions'), - 'acl_m_report' => array('lang' => 'ACL_M_REPORT', 'cat' => 'post_actions'), - 'acl_m_chgposter' => array('lang' => 'ACL_M_CHGPOSTER', 'cat' => 'post_actions'), + 'm_edit' => array('lang' => 'ACL_M_EDIT', 'cat' => 'post_actions'), + 'm_delete' => array('lang' => 'ACL_M_DELETE', 'cat' => 'post_actions'), + 'm_approve' => array('lang' => 'ACL_M_APPROVE', 'cat' => 'post_actions'), + 'm_report' => array('lang' => 'ACL_M_REPORT', 'cat' => 'post_actions'), + 'm_chgposter' => array('lang' => 'ACL_M_CHGPOSTER', 'cat' => 'post_actions'), - 'acl_m_move' => array('lang' => 'ACL_M_MOVE', 'cat' => 'topic_actions'), - 'acl_m_lock' => array('lang' => 'ACL_M_LOCK', 'cat' => 'topic_actions'), - 'acl_m_split' => array('lang' => 'ACL_M_SPLIT', 'cat' => 'topic_actions'), - 'acl_m_merge' => array('lang' => 'ACL_M_MERGE', 'cat' => 'topic_actions'), + 'm_move' => array('lang' => 'ACL_M_MOVE', 'cat' => 'topic_actions'), + 'm_lock' => array('lang' => 'ACL_M_LOCK', 'cat' => 'topic_actions'), + 'm_split' => array('lang' => 'ACL_M_SPLIT', 'cat' => 'topic_actions'), + 'm_merge' => array('lang' => 'ACL_M_MERGE', 'cat' => 'topic_actions'), - 'acl_m_info' => array('lang' => 'ACL_M_INFO', 'cat' => 'misc'), - 'acl_m_warn' => array('lang' => 'ACL_M_WARN', 'cat' => 'misc'), - 'acl_m_ban' => array('lang' => 'ACL_M_BAN', 'cat' => 'misc'), + 'm_info' => array('lang' => 'ACL_M_INFO', 'cat' => 'misc'), + 'm_warn' => array('lang' => 'ACL_M_WARN', 'cat' => 'misc'), + 'm_ban' => array('lang' => 'ACL_M_BAN', 'cat' => 'misc'), // Admin Permissions - 'acl_a_board' => array('lang' => 'ACL_A_BOARD', 'cat' => 'settings'), - 'acl_a_server' => array('lang' => 'ACL_A_SERVER', 'cat' => 'settings'), - 'acl_a_jabber' => array('lang' => 'ACL_A_JABBER', 'cat' => 'settings'), - 'acl_a_phpinfo' => array('lang' => 'ACL_A_PHPINFO', 'cat' => 'settings'), - - 'acl_a_forum' => array('lang' => 'ACL_A_FORUM', 'cat' => 'forums'), - 'acl_a_forumadd' => array('lang' => 'ACL_A_FORUMADD', 'cat' => 'forums'), - 'acl_a_forumdel' => array('lang' => 'ACL_A_FORUMDEL', 'cat' => 'forums'), - 'acl_a_prune' => array('lang' => 'ACL_A_PRUNE', 'cat' => 'forums'), - - 'acl_a_icons' => array('lang' => 'ACL_A_ICONS', 'cat' => 'posting'), - 'acl_a_words' => array('lang' => 'ACL_A_WORDS', 'cat' => 'posting'), - 'acl_a_bbcode' => array('lang' => 'ACL_A_BBCODE', 'cat' => 'posting'), - 'acl_a_attach' => array('lang' => 'ACL_A_ATTACH', 'cat' => 'posting'), - - 'acl_a_user' => array('lang' => 'ACL_A_USER', 'cat' => 'user_group'), - 'acl_a_userdel' => array('lang' => 'ACL_A_USERDEL', 'cat' => 'user_group'), - 'acl_a_group' => array('lang' => 'ACL_A_GROUP', 'cat' => 'user_group'), - 'acl_a_groupadd' => array('lang' => 'ACL_A_GROUPADD', 'cat' => 'user_group'), - 'acl_a_groupdel' => array('lang' => 'ACL_A_GROUPDEL', 'cat' => 'user_group'), - 'acl_a_ranks' => array('lang' => 'ACL_A_RANKS', 'cat' => 'user_group'), - 'acl_a_profile' => array('lang' => 'ACL_A_PROFILE', 'cat' => 'user_group'), - 'acl_a_names' => array('lang' => 'ACL_A_NAMES', 'cat' => 'user_group'), - 'acl_a_ban' => array('lang' => 'ACL_A_BAN', 'cat' => 'user_group'), - - 'acl_a_viewauth' => array('lang' => 'ACL_A_VIEWAUTH', 'cat' => 'permissions'), - 'acl_a_authgroups' => array('lang' => 'ACL_A_AUTHGROUPS', 'cat' => 'permissions'), - 'acl_a_authusers' => array('lang' => 'ACL_A_AUTHUSERS', 'cat' => 'permissions'), - 'acl_a_fauth' => array('lang' => 'ACL_A_FAUTH', 'cat' => 'permissions'), - 'acl_a_mauth' => array('lang' => 'ACL_A_MAUTH', 'cat' => 'permissions'), - 'acl_a_aauth' => array('lang' => 'ACL_A_AAUTH', 'cat' => 'permissions'), - 'acl_a_uauth' => array('lang' => 'ACL_A_UAUTH', 'cat' => 'permissions'), - 'acl_a_roles' => array('lang' => 'ACL_A_ROLES', 'cat' => 'permissions'), - 'acl_a_switchperm' => array('lang' => 'ACL_A_SWITCHPERM', 'cat' => 'permissions'), - - 'acl_a_styles' => array('lang' => 'ACL_A_STYLES', 'cat' => 'misc'), - 'acl_a_extensions' => array('lang' => 'ACL_A_EXTENSIONS', 'cat' => 'misc'), - 'acl_a_viewlogs' => array('lang' => 'ACL_A_VIEWLOGS', 'cat' => 'misc'), - 'acl_a_clearlogs' => array('lang' => 'ACL_A_CLEARLOGS', 'cat' => 'misc'), - 'acl_a_modules' => array('lang' => 'ACL_A_MODULES', 'cat' => 'misc'), - 'acl_a_language' => array('lang' => 'ACL_A_LANGUAGE', 'cat' => 'misc'), - 'acl_a_email' => array('lang' => 'ACL_A_EMAIL', 'cat' => 'misc'), - 'acl_a_bots' => array('lang' => 'ACL_A_BOTS', 'cat' => 'misc'), - 'acl_a_reasons' => array('lang' => 'ACL_A_REASONS', 'cat' => 'misc'), - 'acl_a_backup' => array('lang' => 'ACL_A_BACKUP', 'cat' => 'misc'), - 'acl_a_search' => array('lang' => 'ACL_A_SEARCH', 'cat' => 'misc'), + 'a_board' => array('lang' => 'ACL_A_BOARD', 'cat' => 'settings'), + 'a_server' => array('lang' => 'ACL_A_SERVER', 'cat' => 'settings'), + 'a_jabber' => array('lang' => 'ACL_A_JABBER', 'cat' => 'settings'), + 'a_phpinfo' => array('lang' => 'ACL_A_PHPINFO', 'cat' => 'settings'), + + 'a_forum' => array('lang' => 'ACL_A_FORUM', 'cat' => 'forums'), + 'a_forumadd' => array('lang' => 'ACL_A_FORUMADD', 'cat' => 'forums'), + 'a_forumdel' => array('lang' => 'ACL_A_FORUMDEL', 'cat' => 'forums'), + 'a_prune' => array('lang' => 'ACL_A_PRUNE', 'cat' => 'forums'), + + 'a_icons' => array('lang' => 'ACL_A_ICONS', 'cat' => 'posting'), + 'a_words' => array('lang' => 'ACL_A_WORDS', 'cat' => 'posting'), + 'a_bbcode' => array('lang' => 'ACL_A_BBCODE', 'cat' => 'posting'), + 'a_attach' => array('lang' => 'ACL_A_ATTACH', 'cat' => 'posting'), + + 'a_user' => array('lang' => 'ACL_A_USER', 'cat' => 'user_group'), + 'a_userdel' => array('lang' => 'ACL_A_USERDEL', 'cat' => 'user_group'), + 'a_group' => array('lang' => 'ACL_A_GROUP', 'cat' => 'user_group'), + 'a_groupadd' => array('lang' => 'ACL_A_GROUPADD', 'cat' => 'user_group'), + 'a_groupdel' => array('lang' => 'ACL_A_GROUPDEL', 'cat' => 'user_group'), + 'a_ranks' => array('lang' => 'ACL_A_RANKS', 'cat' => 'user_group'), + 'a_profile' => array('lang' => 'ACL_A_PROFILE', 'cat' => 'user_group'), + 'a_names' => array('lang' => 'ACL_A_NAMES', 'cat' => 'user_group'), + 'a_ban' => array('lang' => 'ACL_A_BAN', 'cat' => 'user_group'), + + 'a_viewauth' => array('lang' => 'ACL_A_VIEWAUTH', 'cat' => 'permissions'), + 'a_authgroups' => array('lang' => 'ACL_A_AUTHGROUPS', 'cat' => 'permissions'), + 'a_authusers' => array('lang' => 'ACL_A_AUTHUSERS', 'cat' => 'permissions'), + 'a_fauth' => array('lang' => 'ACL_A_FAUTH', 'cat' => 'permissions'), + 'a_mauth' => array('lang' => 'ACL_A_MAUTH', 'cat' => 'permissions'), + 'a_aauth' => array('lang' => 'ACL_A_AAUTH', 'cat' => 'permissions'), + 'a_uauth' => array('lang' => 'ACL_A_UAUTH', 'cat' => 'permissions'), + 'a_roles' => array('lang' => 'ACL_A_ROLES', 'cat' => 'permissions'), + 'a_switchperm' => array('lang' => 'ACL_A_SWITCHPERM', 'cat' => 'permissions'), + + 'a_styles' => array('lang' => 'ACL_A_STYLES', 'cat' => 'misc'), + 'a_extensions' => array('lang' => 'ACL_A_EXTENSIONS', 'cat' => 'misc'), + 'a_viewlogs' => array('lang' => 'ACL_A_VIEWLOGS', 'cat' => 'misc'), + 'a_clearlogs' => array('lang' => 'ACL_A_CLEARLOGS', 'cat' => 'misc'), + 'a_modules' => array('lang' => 'ACL_A_MODULES', 'cat' => 'misc'), + 'a_language' => array('lang' => 'ACL_A_LANGUAGE', 'cat' => 'misc'), + 'a_email' => array('lang' => 'ACL_A_EMAIL', 'cat' => 'misc'), + 'a_bots' => array('lang' => 'ACL_A_BOTS', 'cat' => 'misc'), + 'a_reasons' => array('lang' => 'ACL_A_REASONS', 'cat' => 'misc'), + 'a_backup' => array('lang' => 'ACL_A_BACKUP', 'cat' => 'misc'), + 'a_search' => array('lang' => 'ACL_A_SEARCH', 'cat' => 'misc'), ); } -- cgit v1.2.1 From 4b7b7e895b4a0949d19a7524bedddfa969a33ee7 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Wed, 10 Jul 2013 16:44:24 +0200 Subject: [ticket/11582] Fix missing @params in the doc blocks PHPBB3-11582 --- phpBB/includes/permissions.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'phpBB') diff --git a/phpBB/includes/permissions.php b/phpBB/includes/permissions.php index 1db6843dbb..66360424ea 100644 --- a/phpBB/includes/permissions.php +++ b/phpBB/includes/permissions.php @@ -86,7 +86,8 @@ class phpbb_permissions /** * Returns the language string of a permission category * - * @return string Language string + * @param string $category Identifier of the category + * @return string Language string */ public function get_category_lang($category) { @@ -106,6 +107,8 @@ class phpbb_permissions /** * Returns the language string of a permission type * + * @param string $type Identifier of the type + * @param mixed $scope Scope of the type (should be 'global', 'local' or false) * @return string Language string */ public function get_type_lang($type, $scope = false) @@ -151,7 +154,8 @@ class phpbb_permissions /** * Returns the category of a permission * - * @return string + * @param string $permission Identifier of the permission + * @return string Returns the category identifier of the permission */ public function get_permission_category($permission) { @@ -161,6 +165,7 @@ class phpbb_permissions /** * Returns the language string of a permission * + * @param string $permission Identifier of the permission * @return string Language string */ public function get_permission_lang($permission) -- cgit v1.2.1 From 060754fd6ce998caf8b8e182f53a1464e16e9deb Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Fri, 12 Jul 2013 00:05:48 -0400 Subject: [ticket/11582] Fix missing closing bracket PHPBB3-11582 --- phpBB/includes/acp/acp_permission_roles.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'phpBB') diff --git a/phpBB/includes/acp/acp_permission_roles.php b/phpBB/includes/acp/acp_permission_roles.php index 7c972c74ba..5657cbe675 100644 --- a/phpBB/includes/acp/acp_permission_roles.php +++ b/phpBB/includes/acp/acp_permission_roles.php @@ -492,8 +492,8 @@ class acp_permission_roles 'S_NO' => ($allowed == ACL_NO) ? true : false, 'FIELD_NAME' => $permission, - 'PERMISSION' => $phpbb_permissions->get_permission_lang($permission) - ); + 'PERMISSION' => $phpbb_permissions->get_permission_lang($permission), + )); } } } -- cgit v1.2.1 From 9c72bbe284514c1aa70f8ac65e9dfcafb72d36dd Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Sun, 14 Jul 2013 12:04:04 -0400 Subject: [ticket/11582] Move file to new directory PHPBB3-11582 --- phpBB/includes/permissions.php | 340 ----------------------------------------- phpBB/phpbb/permissions.php | 340 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 340 insertions(+), 340 deletions(-) delete mode 100644 phpBB/includes/permissions.php create mode 100644 phpBB/phpbb/permissions.php (limited to 'phpBB') diff --git a/phpBB/includes/permissions.php b/phpBB/includes/permissions.php deleted file mode 100644 index 66360424ea..0000000000 --- a/phpBB/includes/permissions.php +++ /dev/null @@ -1,340 +0,0 @@ -dispatcher = $phpbb_dispatcher; - $this->user = $user; - - $categories = $this->categories; - $types = $this->types; - $permissions = $this->permissions; - - /** - * Allows to specify additional permission categories, types and permissions - * - * @event core.permissions - * @var array types Array with permission types (a_, u_, m_, etc.) - * @var array categories Array with permission categories (pm, post, settings, misc, etc.) - * @var array permissions Array with permissions. Each Permission has the following layout: - * '' => array( - * 'lang' => 'Language Key with a Short description', // Optional, if not set, - * // the permissions identifier '' is used with - * // all uppercase. - * 'cat' => 'Identifier of the category, the permission should be displayed in', - * ), - * Example: - * 'u_viewprofile' => array( - * 'lang' => 'ACL_U_VIEWPROFILE', - * 'cat' => 'profile', - * ), - * @since 3.1-A1 - */ - $vars = array('types', 'categories', 'permissions'); - extract($phpbb_dispatcher->trigger_event('core.permissions', $vars)); - - $this->categories = $categories; - $this->types = $types; - $this->permissions = $permissions; - } - - /** - * Returns an array with all the permission categories (pm, post, settings, misc, etc.) - * - * @return array Layout: cat-identifier => Language key - */ - public function get_categories() - { - return $this->categories; - } - - /** - * Returns the language string of a permission category - * - * @param string $category Identifier of the category - * @return string Language string - */ - public function get_category_lang($category) - { - return $this->user->lang($this->categories[$category]); - } - - /** - * Returns an array with all the permission types (a_, u_, m_, etc.) - * - * @return array Layout: type-identifier => Language key - */ - public function get_types() - { - return $this->types; - } - - /** - * Returns the language string of a permission type - * - * @param string $type Identifier of the type - * @param mixed $scope Scope of the type (should be 'global', 'local' or false) - * @return string Language string - */ - public function get_type_lang($type, $scope = false) - { - if ($scope && isset($this->types[$scope][$type])) - { - $lang_key = $this->types[$scope][$type]; - } - else if (isset($this->types[$type])) - { - $lang_key = $this->types[$type]; - } - else - { - $lang_key = 'ACL_TYPE_' . strtoupper(($scope) ? $scope . '_' . $type : $type); - } - - return $this->user->lang($lang_key); - } - - /** - * Returns an array with all the permissions. - * Each Permission has the following layout: - * '' => array( - * 'lang' => 'Language Key with a Short description', // Optional, if not set, - * // the permissions identifier '' is used with - * // all uppercase. - * 'cat' => 'Identifier of the category, the permission should be displayed in', - * ), - * Example: - * 'u_viewprofile' => array( - * 'lang' => 'ACL_U_VIEWPROFILE', - * 'cat' => 'profile', - * ), - * - * @return array - */ - public function get_permissions() - { - return $this->permissions; - } - - /** - * Returns the category of a permission - * - * @param string $permission Identifier of the permission - * @return string Returns the category identifier of the permission - */ - public function get_permission_category($permission) - { - return (isset($this->permissions[$permission]['cat'])) ? $this->permissions[$permission]['cat'] : 'misc'; - } - - /** - * Returns the language string of a permission - * - * @param string $permission Identifier of the permission - * @return string Language string - */ - public function get_permission_lang($permission) - { - return (isset($this->permissions[$permission]['lang'])) ? $this->user->lang($this->permissions[$permission]['lang']) : $this->user->lang('ACL_' . strtoupper($permission)); - } - - protected $types = array( - 'u_' => 'ACL_TYPE_U_', - 'a_' => 'ACL_TYPE_A_', - 'm_' => 'ACL_TYPE_M_', - 'f_' => 'ACL_TYPE_F_', - 'global' => array( - 'm_' => 'ACL_TYPE_GLOBAL_M_', - ), - ); - - protected $categories = array( - 'actions' => 'ACL_CAT_ACTIONS', - 'content' => 'ACL_CAT_CONTENT', - 'forums' => 'ACL_CAT_FORUMS', - 'misc' => 'ACL_CAT_MISC', - 'permissions' => 'ACL_CAT_PERMISSIONS', - 'pm' => 'ACL_CAT_PM', - 'polls' => 'ACL_CAT_POLLS', - 'post' => 'ACL_CAT_POST', - 'post_actions' => 'ACL_CAT_POST_ACTIONS', - 'posting' => 'ACL_CAT_POSTING', - 'profile' => 'ACL_CAT_PROFILE', - 'settings' => 'ACL_CAT_SETTINGS', - 'topic_actions' => 'ACL_CAT_TOPIC_ACTIONS', - 'user_group' => 'ACL_CAT_USER_GROUP', - ); - - protected $permissions = array( - // User Permissions - 'u_viewprofile' => array('lang' => 'ACL_U_VIEWPROFILE', 'cat' => 'profile'), - 'u_chgname' => array('lang' => 'ACL_U_CHGNAME', 'cat' => 'profile'), - 'u_chgpasswd' => array('lang' => 'ACL_U_CHGPASSWD', 'cat' => 'profile'), - 'u_chgemail' => array('lang' => 'ACL_U_CHGEMAIL', 'cat' => 'profile'), - 'u_chgavatar' => array('lang' => 'ACL_U_CHGAVATAR', 'cat' => 'profile'), - 'u_chggrp' => array('lang' => 'ACL_U_CHGGRP', 'cat' => 'profile'), - 'u_chgprofileinfo' => array('lang' => 'ACL_U_CHGPROFILEINFO', 'cat' => 'profile'), - - 'u_attach' => array('lang' => 'ACL_U_ATTACH', 'cat' => 'post'), - 'u_download' => array('lang' => 'ACL_U_DOWNLOAD', 'cat' => 'post'), - 'u_savedrafts' => array('lang' => 'ACL_U_SAVEDRAFTS', 'cat' => 'post'), - 'u_chgcensors' => array('lang' => 'ACL_U_CHGCENSORS', 'cat' => 'post'), - 'u_sig' => array('lang' => 'ACL_U_SIG', 'cat' => 'post'), - - 'u_sendpm' => array('lang' => 'ACL_U_SENDPM', 'cat' => 'pm'), - 'u_masspm' => array('lang' => 'ACL_U_MASSPM', 'cat' => 'pm'), - 'u_masspm_group'=> array('lang' => 'ACL_U_MASSPM_GROUP', 'cat' => 'pm'), - 'u_readpm' => array('lang' => 'ACL_U_READPM', 'cat' => 'pm'), - 'u_pm_edit' => array('lang' => 'ACL_U_PM_EDIT', 'cat' => 'pm'), - 'u_pm_delete' => array('lang' => 'ACL_U_PM_DELETE', 'cat' => 'pm'), - 'u_pm_forward' => array('lang' => 'ACL_U_PM_FORWARD', 'cat' => 'pm'), - 'u_pm_emailpm' => array('lang' => 'ACL_U_PM_EMAILPM', 'cat' => 'pm'), - 'u_pm_printpm' => array('lang' => 'ACL_U_PM_PRINTPM', 'cat' => 'pm'), - 'u_pm_attach' => array('lang' => 'ACL_U_PM_ATTACH', 'cat' => 'pm'), - 'u_pm_download' => array('lang' => 'ACL_U_PM_DOWNLOAD', 'cat' => 'pm'), - 'u_pm_bbcode' => array('lang' => 'ACL_U_PM_BBCODE', 'cat' => 'pm'), - 'u_pm_smilies' => array('lang' => 'ACL_U_PM_SMILIES', 'cat' => 'pm'), - 'u_pm_img' => array('lang' => 'ACL_U_PM_IMG', 'cat' => 'pm'), - 'u_pm_flash' => array('lang' => 'ACL_U_PM_FLASH', 'cat' => 'pm'), - - 'u_sendemail' => array('lang' => 'ACL_U_SENDEMAIL', 'cat' => 'misc'), - 'u_sendim' => array('lang' => 'ACL_U_SENDIM', 'cat' => 'misc'), - 'u_ignoreflood' => array('lang' => 'ACL_U_IGNOREFLOOD', 'cat' => 'misc'), - 'u_hideonline' => array('lang' => 'ACL_U_HIDEONLINE', 'cat' => 'misc'), - 'u_viewonline' => array('lang' => 'ACL_U_VIEWONLINE', 'cat' => 'misc'), - 'u_search' => array('lang' => 'ACL_U_SEARCH', 'cat' => 'misc'), - - // Forum Permissions - 'f_list' => array('lang' => 'ACL_F_LIST', 'cat' => 'actions'), - 'f_read' => array('lang' => 'ACL_F_READ', 'cat' => 'actions'), - 'f_search' => array('lang' => 'ACL_F_SEARCH', 'cat' => 'actions'), - 'f_subscribe' => array('lang' => 'ACL_F_SUBSCRIBE', 'cat' => 'actions'), - 'f_print' => array('lang' => 'ACL_F_PRINT', 'cat' => 'actions'), - 'f_email' => array('lang' => 'ACL_F_EMAIL', 'cat' => 'actions'), - 'f_bump' => array('lang' => 'ACL_F_BUMP', 'cat' => 'actions'), - 'f_user_lock' => array('lang' => 'ACL_F_USER_LOCK', 'cat' => 'actions'), - 'f_download' => array('lang' => 'ACL_F_DOWNLOAD', 'cat' => 'actions'), - 'f_report' => array('lang' => 'ACL_F_REPORT', 'cat' => 'actions'), - - 'f_post' => array('lang' => 'ACL_F_POST', 'cat' => 'post'), - 'f_sticky' => array('lang' => 'ACL_F_STICKY', 'cat' => 'post'), - 'f_announce' => array('lang' => 'ACL_F_ANNOUNCE', 'cat' => 'post'), - 'f_reply' => array('lang' => 'ACL_F_REPLY', 'cat' => 'post'), - 'f_edit' => array('lang' => 'ACL_F_EDIT', 'cat' => 'post'), - 'f_delete' => array('lang' => 'ACL_F_DELETE', 'cat' => 'post'), - 'f_ignoreflood' => array('lang' => 'ACL_F_IGNOREFLOOD', 'cat' => 'post'), - 'f_postcount' => array('lang' => 'ACL_F_POSTCOUNT', 'cat' => 'post'), - 'f_noapprove' => array('lang' => 'ACL_F_NOAPPROVE', 'cat' => 'post'), - - 'f_attach' => array('lang' => 'ACL_F_ATTACH', 'cat' => 'content'), - 'f_icons' => array('lang' => 'ACL_F_ICONS', 'cat' => 'content'), - 'f_bbcode' => array('lang' => 'ACL_F_BBCODE', 'cat' => 'content'), - 'f_flash' => array('lang' => 'ACL_F_FLASH', 'cat' => 'content'), - 'f_img' => array('lang' => 'ACL_F_IMG', 'cat' => 'content'), - 'f_sigs' => array('lang' => 'ACL_F_SIGS', 'cat' => 'content'), - 'f_smilies' => array('lang' => 'ACL_F_SMILIES', 'cat' => 'content'), - - 'f_poll' => array('lang' => 'ACL_F_POLL', 'cat' => 'polls'), - 'f_vote' => array('lang' => 'ACL_F_VOTE', 'cat' => 'polls'), - 'f_votechg' => array('lang' => 'ACL_F_VOTECHG', 'cat' => 'polls'), - - // Moderator Permissions - 'm_edit' => array('lang' => 'ACL_M_EDIT', 'cat' => 'post_actions'), - 'm_delete' => array('lang' => 'ACL_M_DELETE', 'cat' => 'post_actions'), - 'm_approve' => array('lang' => 'ACL_M_APPROVE', 'cat' => 'post_actions'), - 'm_report' => array('lang' => 'ACL_M_REPORT', 'cat' => 'post_actions'), - 'm_chgposter' => array('lang' => 'ACL_M_CHGPOSTER', 'cat' => 'post_actions'), - - 'm_move' => array('lang' => 'ACL_M_MOVE', 'cat' => 'topic_actions'), - 'm_lock' => array('lang' => 'ACL_M_LOCK', 'cat' => 'topic_actions'), - 'm_split' => array('lang' => 'ACL_M_SPLIT', 'cat' => 'topic_actions'), - 'm_merge' => array('lang' => 'ACL_M_MERGE', 'cat' => 'topic_actions'), - - 'm_info' => array('lang' => 'ACL_M_INFO', 'cat' => 'misc'), - 'm_warn' => array('lang' => 'ACL_M_WARN', 'cat' => 'misc'), - 'm_ban' => array('lang' => 'ACL_M_BAN', 'cat' => 'misc'), - - // Admin Permissions - 'a_board' => array('lang' => 'ACL_A_BOARD', 'cat' => 'settings'), - 'a_server' => array('lang' => 'ACL_A_SERVER', 'cat' => 'settings'), - 'a_jabber' => array('lang' => 'ACL_A_JABBER', 'cat' => 'settings'), - 'a_phpinfo' => array('lang' => 'ACL_A_PHPINFO', 'cat' => 'settings'), - - 'a_forum' => array('lang' => 'ACL_A_FORUM', 'cat' => 'forums'), - 'a_forumadd' => array('lang' => 'ACL_A_FORUMADD', 'cat' => 'forums'), - 'a_forumdel' => array('lang' => 'ACL_A_FORUMDEL', 'cat' => 'forums'), - 'a_prune' => array('lang' => 'ACL_A_PRUNE', 'cat' => 'forums'), - - 'a_icons' => array('lang' => 'ACL_A_ICONS', 'cat' => 'posting'), - 'a_words' => array('lang' => 'ACL_A_WORDS', 'cat' => 'posting'), - 'a_bbcode' => array('lang' => 'ACL_A_BBCODE', 'cat' => 'posting'), - 'a_attach' => array('lang' => 'ACL_A_ATTACH', 'cat' => 'posting'), - - 'a_user' => array('lang' => 'ACL_A_USER', 'cat' => 'user_group'), - 'a_userdel' => array('lang' => 'ACL_A_USERDEL', 'cat' => 'user_group'), - 'a_group' => array('lang' => 'ACL_A_GROUP', 'cat' => 'user_group'), - 'a_groupadd' => array('lang' => 'ACL_A_GROUPADD', 'cat' => 'user_group'), - 'a_groupdel' => array('lang' => 'ACL_A_GROUPDEL', 'cat' => 'user_group'), - 'a_ranks' => array('lang' => 'ACL_A_RANKS', 'cat' => 'user_group'), - 'a_profile' => array('lang' => 'ACL_A_PROFILE', 'cat' => 'user_group'), - 'a_names' => array('lang' => 'ACL_A_NAMES', 'cat' => 'user_group'), - 'a_ban' => array('lang' => 'ACL_A_BAN', 'cat' => 'user_group'), - - 'a_viewauth' => array('lang' => 'ACL_A_VIEWAUTH', 'cat' => 'permissions'), - 'a_authgroups' => array('lang' => 'ACL_A_AUTHGROUPS', 'cat' => 'permissions'), - 'a_authusers' => array('lang' => 'ACL_A_AUTHUSERS', 'cat' => 'permissions'), - 'a_fauth' => array('lang' => 'ACL_A_FAUTH', 'cat' => 'permissions'), - 'a_mauth' => array('lang' => 'ACL_A_MAUTH', 'cat' => 'permissions'), - 'a_aauth' => array('lang' => 'ACL_A_AAUTH', 'cat' => 'permissions'), - 'a_uauth' => array('lang' => 'ACL_A_UAUTH', 'cat' => 'permissions'), - 'a_roles' => array('lang' => 'ACL_A_ROLES', 'cat' => 'permissions'), - 'a_switchperm' => array('lang' => 'ACL_A_SWITCHPERM', 'cat' => 'permissions'), - - 'a_styles' => array('lang' => 'ACL_A_STYLES', 'cat' => 'misc'), - 'a_extensions' => array('lang' => 'ACL_A_EXTENSIONS', 'cat' => 'misc'), - 'a_viewlogs' => array('lang' => 'ACL_A_VIEWLOGS', 'cat' => 'misc'), - 'a_clearlogs' => array('lang' => 'ACL_A_CLEARLOGS', 'cat' => 'misc'), - 'a_modules' => array('lang' => 'ACL_A_MODULES', 'cat' => 'misc'), - 'a_language' => array('lang' => 'ACL_A_LANGUAGE', 'cat' => 'misc'), - 'a_email' => array('lang' => 'ACL_A_EMAIL', 'cat' => 'misc'), - 'a_bots' => array('lang' => 'ACL_A_BOTS', 'cat' => 'misc'), - 'a_reasons' => array('lang' => 'ACL_A_REASONS', 'cat' => 'misc'), - 'a_backup' => array('lang' => 'ACL_A_BACKUP', 'cat' => 'misc'), - 'a_search' => array('lang' => 'ACL_A_SEARCH', 'cat' => 'misc'), - ); -} diff --git a/phpBB/phpbb/permissions.php b/phpBB/phpbb/permissions.php new file mode 100644 index 0000000000..66360424ea --- /dev/null +++ b/phpBB/phpbb/permissions.php @@ -0,0 +1,340 @@ +dispatcher = $phpbb_dispatcher; + $this->user = $user; + + $categories = $this->categories; + $types = $this->types; + $permissions = $this->permissions; + + /** + * Allows to specify additional permission categories, types and permissions + * + * @event core.permissions + * @var array types Array with permission types (a_, u_, m_, etc.) + * @var array categories Array with permission categories (pm, post, settings, misc, etc.) + * @var array permissions Array with permissions. Each Permission has the following layout: + * '' => array( + * 'lang' => 'Language Key with a Short description', // Optional, if not set, + * // the permissions identifier '' is used with + * // all uppercase. + * 'cat' => 'Identifier of the category, the permission should be displayed in', + * ), + * Example: + * 'u_viewprofile' => array( + * 'lang' => 'ACL_U_VIEWPROFILE', + * 'cat' => 'profile', + * ), + * @since 3.1-A1 + */ + $vars = array('types', 'categories', 'permissions'); + extract($phpbb_dispatcher->trigger_event('core.permissions', $vars)); + + $this->categories = $categories; + $this->types = $types; + $this->permissions = $permissions; + } + + /** + * Returns an array with all the permission categories (pm, post, settings, misc, etc.) + * + * @return array Layout: cat-identifier => Language key + */ + public function get_categories() + { + return $this->categories; + } + + /** + * Returns the language string of a permission category + * + * @param string $category Identifier of the category + * @return string Language string + */ + public function get_category_lang($category) + { + return $this->user->lang($this->categories[$category]); + } + + /** + * Returns an array with all the permission types (a_, u_, m_, etc.) + * + * @return array Layout: type-identifier => Language key + */ + public function get_types() + { + return $this->types; + } + + /** + * Returns the language string of a permission type + * + * @param string $type Identifier of the type + * @param mixed $scope Scope of the type (should be 'global', 'local' or false) + * @return string Language string + */ + public function get_type_lang($type, $scope = false) + { + if ($scope && isset($this->types[$scope][$type])) + { + $lang_key = $this->types[$scope][$type]; + } + else if (isset($this->types[$type])) + { + $lang_key = $this->types[$type]; + } + else + { + $lang_key = 'ACL_TYPE_' . strtoupper(($scope) ? $scope . '_' . $type : $type); + } + + return $this->user->lang($lang_key); + } + + /** + * Returns an array with all the permissions. + * Each Permission has the following layout: + * '' => array( + * 'lang' => 'Language Key with a Short description', // Optional, if not set, + * // the permissions identifier '' is used with + * // all uppercase. + * 'cat' => 'Identifier of the category, the permission should be displayed in', + * ), + * Example: + * 'u_viewprofile' => array( + * 'lang' => 'ACL_U_VIEWPROFILE', + * 'cat' => 'profile', + * ), + * + * @return array + */ + public function get_permissions() + { + return $this->permissions; + } + + /** + * Returns the category of a permission + * + * @param string $permission Identifier of the permission + * @return string Returns the category identifier of the permission + */ + public function get_permission_category($permission) + { + return (isset($this->permissions[$permission]['cat'])) ? $this->permissions[$permission]['cat'] : 'misc'; + } + + /** + * Returns the language string of a permission + * + * @param string $permission Identifier of the permission + * @return string Language string + */ + public function get_permission_lang($permission) + { + return (isset($this->permissions[$permission]['lang'])) ? $this->user->lang($this->permissions[$permission]['lang']) : $this->user->lang('ACL_' . strtoupper($permission)); + } + + protected $types = array( + 'u_' => 'ACL_TYPE_U_', + 'a_' => 'ACL_TYPE_A_', + 'm_' => 'ACL_TYPE_M_', + 'f_' => 'ACL_TYPE_F_', + 'global' => array( + 'm_' => 'ACL_TYPE_GLOBAL_M_', + ), + ); + + protected $categories = array( + 'actions' => 'ACL_CAT_ACTIONS', + 'content' => 'ACL_CAT_CONTENT', + 'forums' => 'ACL_CAT_FORUMS', + 'misc' => 'ACL_CAT_MISC', + 'permissions' => 'ACL_CAT_PERMISSIONS', + 'pm' => 'ACL_CAT_PM', + 'polls' => 'ACL_CAT_POLLS', + 'post' => 'ACL_CAT_POST', + 'post_actions' => 'ACL_CAT_POST_ACTIONS', + 'posting' => 'ACL_CAT_POSTING', + 'profile' => 'ACL_CAT_PROFILE', + 'settings' => 'ACL_CAT_SETTINGS', + 'topic_actions' => 'ACL_CAT_TOPIC_ACTIONS', + 'user_group' => 'ACL_CAT_USER_GROUP', + ); + + protected $permissions = array( + // User Permissions + 'u_viewprofile' => array('lang' => 'ACL_U_VIEWPROFILE', 'cat' => 'profile'), + 'u_chgname' => array('lang' => 'ACL_U_CHGNAME', 'cat' => 'profile'), + 'u_chgpasswd' => array('lang' => 'ACL_U_CHGPASSWD', 'cat' => 'profile'), + 'u_chgemail' => array('lang' => 'ACL_U_CHGEMAIL', 'cat' => 'profile'), + 'u_chgavatar' => array('lang' => 'ACL_U_CHGAVATAR', 'cat' => 'profile'), + 'u_chggrp' => array('lang' => 'ACL_U_CHGGRP', 'cat' => 'profile'), + 'u_chgprofileinfo' => array('lang' => 'ACL_U_CHGPROFILEINFO', 'cat' => 'profile'), + + 'u_attach' => array('lang' => 'ACL_U_ATTACH', 'cat' => 'post'), + 'u_download' => array('lang' => 'ACL_U_DOWNLOAD', 'cat' => 'post'), + 'u_savedrafts' => array('lang' => 'ACL_U_SAVEDRAFTS', 'cat' => 'post'), + 'u_chgcensors' => array('lang' => 'ACL_U_CHGCENSORS', 'cat' => 'post'), + 'u_sig' => array('lang' => 'ACL_U_SIG', 'cat' => 'post'), + + 'u_sendpm' => array('lang' => 'ACL_U_SENDPM', 'cat' => 'pm'), + 'u_masspm' => array('lang' => 'ACL_U_MASSPM', 'cat' => 'pm'), + 'u_masspm_group'=> array('lang' => 'ACL_U_MASSPM_GROUP', 'cat' => 'pm'), + 'u_readpm' => array('lang' => 'ACL_U_READPM', 'cat' => 'pm'), + 'u_pm_edit' => array('lang' => 'ACL_U_PM_EDIT', 'cat' => 'pm'), + 'u_pm_delete' => array('lang' => 'ACL_U_PM_DELETE', 'cat' => 'pm'), + 'u_pm_forward' => array('lang' => 'ACL_U_PM_FORWARD', 'cat' => 'pm'), + 'u_pm_emailpm' => array('lang' => 'ACL_U_PM_EMAILPM', 'cat' => 'pm'), + 'u_pm_printpm' => array('lang' => 'ACL_U_PM_PRINTPM', 'cat' => 'pm'), + 'u_pm_attach' => array('lang' => 'ACL_U_PM_ATTACH', 'cat' => 'pm'), + 'u_pm_download' => array('lang' => 'ACL_U_PM_DOWNLOAD', 'cat' => 'pm'), + 'u_pm_bbcode' => array('lang' => 'ACL_U_PM_BBCODE', 'cat' => 'pm'), + 'u_pm_smilies' => array('lang' => 'ACL_U_PM_SMILIES', 'cat' => 'pm'), + 'u_pm_img' => array('lang' => 'ACL_U_PM_IMG', 'cat' => 'pm'), + 'u_pm_flash' => array('lang' => 'ACL_U_PM_FLASH', 'cat' => 'pm'), + + 'u_sendemail' => array('lang' => 'ACL_U_SENDEMAIL', 'cat' => 'misc'), + 'u_sendim' => array('lang' => 'ACL_U_SENDIM', 'cat' => 'misc'), + 'u_ignoreflood' => array('lang' => 'ACL_U_IGNOREFLOOD', 'cat' => 'misc'), + 'u_hideonline' => array('lang' => 'ACL_U_HIDEONLINE', 'cat' => 'misc'), + 'u_viewonline' => array('lang' => 'ACL_U_VIEWONLINE', 'cat' => 'misc'), + 'u_search' => array('lang' => 'ACL_U_SEARCH', 'cat' => 'misc'), + + // Forum Permissions + 'f_list' => array('lang' => 'ACL_F_LIST', 'cat' => 'actions'), + 'f_read' => array('lang' => 'ACL_F_READ', 'cat' => 'actions'), + 'f_search' => array('lang' => 'ACL_F_SEARCH', 'cat' => 'actions'), + 'f_subscribe' => array('lang' => 'ACL_F_SUBSCRIBE', 'cat' => 'actions'), + 'f_print' => array('lang' => 'ACL_F_PRINT', 'cat' => 'actions'), + 'f_email' => array('lang' => 'ACL_F_EMAIL', 'cat' => 'actions'), + 'f_bump' => array('lang' => 'ACL_F_BUMP', 'cat' => 'actions'), + 'f_user_lock' => array('lang' => 'ACL_F_USER_LOCK', 'cat' => 'actions'), + 'f_download' => array('lang' => 'ACL_F_DOWNLOAD', 'cat' => 'actions'), + 'f_report' => array('lang' => 'ACL_F_REPORT', 'cat' => 'actions'), + + 'f_post' => array('lang' => 'ACL_F_POST', 'cat' => 'post'), + 'f_sticky' => array('lang' => 'ACL_F_STICKY', 'cat' => 'post'), + 'f_announce' => array('lang' => 'ACL_F_ANNOUNCE', 'cat' => 'post'), + 'f_reply' => array('lang' => 'ACL_F_REPLY', 'cat' => 'post'), + 'f_edit' => array('lang' => 'ACL_F_EDIT', 'cat' => 'post'), + 'f_delete' => array('lang' => 'ACL_F_DELETE', 'cat' => 'post'), + 'f_ignoreflood' => array('lang' => 'ACL_F_IGNOREFLOOD', 'cat' => 'post'), + 'f_postcount' => array('lang' => 'ACL_F_POSTCOUNT', 'cat' => 'post'), + 'f_noapprove' => array('lang' => 'ACL_F_NOAPPROVE', 'cat' => 'post'), + + 'f_attach' => array('lang' => 'ACL_F_ATTACH', 'cat' => 'content'), + 'f_icons' => array('lang' => 'ACL_F_ICONS', 'cat' => 'content'), + 'f_bbcode' => array('lang' => 'ACL_F_BBCODE', 'cat' => 'content'), + 'f_flash' => array('lang' => 'ACL_F_FLASH', 'cat' => 'content'), + 'f_img' => array('lang' => 'ACL_F_IMG', 'cat' => 'content'), + 'f_sigs' => array('lang' => 'ACL_F_SIGS', 'cat' => 'content'), + 'f_smilies' => array('lang' => 'ACL_F_SMILIES', 'cat' => 'content'), + + 'f_poll' => array('lang' => 'ACL_F_POLL', 'cat' => 'polls'), + 'f_vote' => array('lang' => 'ACL_F_VOTE', 'cat' => 'polls'), + 'f_votechg' => array('lang' => 'ACL_F_VOTECHG', 'cat' => 'polls'), + + // Moderator Permissions + 'm_edit' => array('lang' => 'ACL_M_EDIT', 'cat' => 'post_actions'), + 'm_delete' => array('lang' => 'ACL_M_DELETE', 'cat' => 'post_actions'), + 'm_approve' => array('lang' => 'ACL_M_APPROVE', 'cat' => 'post_actions'), + 'm_report' => array('lang' => 'ACL_M_REPORT', 'cat' => 'post_actions'), + 'm_chgposter' => array('lang' => 'ACL_M_CHGPOSTER', 'cat' => 'post_actions'), + + 'm_move' => array('lang' => 'ACL_M_MOVE', 'cat' => 'topic_actions'), + 'm_lock' => array('lang' => 'ACL_M_LOCK', 'cat' => 'topic_actions'), + 'm_split' => array('lang' => 'ACL_M_SPLIT', 'cat' => 'topic_actions'), + 'm_merge' => array('lang' => 'ACL_M_MERGE', 'cat' => 'topic_actions'), + + 'm_info' => array('lang' => 'ACL_M_INFO', 'cat' => 'misc'), + 'm_warn' => array('lang' => 'ACL_M_WARN', 'cat' => 'misc'), + 'm_ban' => array('lang' => 'ACL_M_BAN', 'cat' => 'misc'), + + // Admin Permissions + 'a_board' => array('lang' => 'ACL_A_BOARD', 'cat' => 'settings'), + 'a_server' => array('lang' => 'ACL_A_SERVER', 'cat' => 'settings'), + 'a_jabber' => array('lang' => 'ACL_A_JABBER', 'cat' => 'settings'), + 'a_phpinfo' => array('lang' => 'ACL_A_PHPINFO', 'cat' => 'settings'), + + 'a_forum' => array('lang' => 'ACL_A_FORUM', 'cat' => 'forums'), + 'a_forumadd' => array('lang' => 'ACL_A_FORUMADD', 'cat' => 'forums'), + 'a_forumdel' => array('lang' => 'ACL_A_FORUMDEL', 'cat' => 'forums'), + 'a_prune' => array('lang' => 'ACL_A_PRUNE', 'cat' => 'forums'), + + 'a_icons' => array('lang' => 'ACL_A_ICONS', 'cat' => 'posting'), + 'a_words' => array('lang' => 'ACL_A_WORDS', 'cat' => 'posting'), + 'a_bbcode' => array('lang' => 'ACL_A_BBCODE', 'cat' => 'posting'), + 'a_attach' => array('lang' => 'ACL_A_ATTACH', 'cat' => 'posting'), + + 'a_user' => array('lang' => 'ACL_A_USER', 'cat' => 'user_group'), + 'a_userdel' => array('lang' => 'ACL_A_USERDEL', 'cat' => 'user_group'), + 'a_group' => array('lang' => 'ACL_A_GROUP', 'cat' => 'user_group'), + 'a_groupadd' => array('lang' => 'ACL_A_GROUPADD', 'cat' => 'user_group'), + 'a_groupdel' => array('lang' => 'ACL_A_GROUPDEL', 'cat' => 'user_group'), + 'a_ranks' => array('lang' => 'ACL_A_RANKS', 'cat' => 'user_group'), + 'a_profile' => array('lang' => 'ACL_A_PROFILE', 'cat' => 'user_group'), + 'a_names' => array('lang' => 'ACL_A_NAMES', 'cat' => 'user_group'), + 'a_ban' => array('lang' => 'ACL_A_BAN', 'cat' => 'user_group'), + + 'a_viewauth' => array('lang' => 'ACL_A_VIEWAUTH', 'cat' => 'permissions'), + 'a_authgroups' => array('lang' => 'ACL_A_AUTHGROUPS', 'cat' => 'permissions'), + 'a_authusers' => array('lang' => 'ACL_A_AUTHUSERS', 'cat' => 'permissions'), + 'a_fauth' => array('lang' => 'ACL_A_FAUTH', 'cat' => 'permissions'), + 'a_mauth' => array('lang' => 'ACL_A_MAUTH', 'cat' => 'permissions'), + 'a_aauth' => array('lang' => 'ACL_A_AAUTH', 'cat' => 'permissions'), + 'a_uauth' => array('lang' => 'ACL_A_UAUTH', 'cat' => 'permissions'), + 'a_roles' => array('lang' => 'ACL_A_ROLES', 'cat' => 'permissions'), + 'a_switchperm' => array('lang' => 'ACL_A_SWITCHPERM', 'cat' => 'permissions'), + + 'a_styles' => array('lang' => 'ACL_A_STYLES', 'cat' => 'misc'), + 'a_extensions' => array('lang' => 'ACL_A_EXTENSIONS', 'cat' => 'misc'), + 'a_viewlogs' => array('lang' => 'ACL_A_VIEWLOGS', 'cat' => 'misc'), + 'a_clearlogs' => array('lang' => 'ACL_A_CLEARLOGS', 'cat' => 'misc'), + 'a_modules' => array('lang' => 'ACL_A_MODULES', 'cat' => 'misc'), + 'a_language' => array('lang' => 'ACL_A_LANGUAGE', 'cat' => 'misc'), + 'a_email' => array('lang' => 'ACL_A_EMAIL', 'cat' => 'misc'), + 'a_bots' => array('lang' => 'ACL_A_BOTS', 'cat' => 'misc'), + 'a_reasons' => array('lang' => 'ACL_A_REASONS', 'cat' => 'misc'), + 'a_backup' => array('lang' => 'ACL_A_BACKUP', 'cat' => 'misc'), + 'a_search' => array('lang' => 'ACL_A_SEARCH', 'cat' => 'misc'), + ); +} -- cgit v1.2.1 From 81e0859041cd181c142a87a5fc78640f92aa5ce0 Mon Sep 17 00:00:00 2001 From: Vjacheslav Trushkin Date: Sun, 14 Jul 2013 12:11:57 -0400 Subject: [ticket/11688] Purge TWIG cache Purge directories Replace opendir() with DirectoryIterator PHPBB3-11688 --- phpBB/phpbb/cache/driver/file.php | 67 +++++++++++++++++++++++++++++++-------- 1 file changed, 54 insertions(+), 13 deletions(-) (limited to 'phpBB') diff --git a/phpBB/phpbb/cache/driver/file.php b/phpBB/phpbb/cache/driver/file.php index 85decbe3e8..7f8c646a11 100644 --- a/phpBB/phpbb/cache/driver/file.php +++ b/phpBB/phpbb/cache/driver/file.php @@ -205,28 +205,36 @@ class phpbb_cache_driver_file extends phpbb_cache_driver_base function purge() { // Purge all phpbb cache files - $dir = @opendir($this->cache_dir); - - if (!$dir) + try + { + $iterator = new DirectoryIterator($this->cache_dir); + } + catch (Exception $e) { return; } - while (($entry = readdir($dir)) !== false) + foreach ($iterator as $fileInfo) { - if (strpos($entry, 'container_') !== 0 && - strpos($entry, 'url_matcher') !== 0 && - strpos($entry, 'sql_') !== 0 && - strpos($entry, 'data_') !== 0 && - strpos($entry, 'ctpl_') !== 0 && - strpos($entry, 'tpl_') !== 0) + if ($fileInfo->isDot()) { continue; } - - $this->remove_file($this->cache_dir . $entry); + $filename = $fileInfo->getFilename(); + if ($fileInfo->isDir()) + { + $this->purge_dir($fileInfo->getPathname()); + } + elseif (strpos($filename, 'container_') === 0 || + strpos($filename, 'url_matcher') === 0 || + strpos($filename, 'sql_') === 0 || + strpos($filename, 'data_') === 0 || + strpos($filename, 'ctpl_') === 0 || + strpos($filename, 'tpl_') === 0) + { + $this->remove_file($fileInfo->getPathname()); + } } - closedir($dir); unset($this->vars); unset($this->var_expires); @@ -241,6 +249,39 @@ class phpbb_cache_driver_file extends phpbb_cache_driver_base $this->is_modified = false; } + /** + * Remove directory + */ + protected function purge_dir($dir) + { + try + { + $iterator = new DirectoryIterator($dir); + } + catch (Exception $e) + { + return; + } + + foreach ($iterator as $fileInfo) + { + if ($fileInfo->isDot()) + { + continue; + } + if ($fileInfo->isDir()) + { + $this->purge_dir($fileInfo->getPathname()); + } + else + { + $this->remove_file($fileInfo->getPathname()); + } + } + + @rmdir($dir); + } + /** * Destroy cache data */ -- cgit v1.2.1 From e4a5ce307d49c215b08e35a79147ba2418f133ff Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Sun, 14 Jul 2013 12:55:03 -0400 Subject: [ticket/11582] Test the event and and fix it. PHPBB3-11582 --- phpBB/phpbb/permissions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB') diff --git a/phpBB/phpbb/permissions.php b/phpBB/phpbb/permissions.php index 66360424ea..0fbacdad8a 100644 --- a/phpBB/phpbb/permissions.php +++ b/phpBB/phpbb/permissions.php @@ -66,7 +66,7 @@ class phpbb_permissions * @since 3.1-A1 */ $vars = array('types', 'categories', 'permissions'); - extract($phpbb_dispatcher->trigger_event('core.permissions', $vars)); + extract($phpbb_dispatcher->trigger_event('core.permissions', compact($vars))); $this->categories = $categories; $this->types = $types; -- cgit v1.2.1 From a68aed51190e10535d364db056de97098cd56019 Mon Sep 17 00:00:00 2001 From: Vjacheslav Trushkin Date: Sun, 14 Jul 2013 13:20:41 -0400 Subject: [ticket/11688] tpl_ files are no longer used Remove tpl_ and ctpl_ from cache->purge() because those prefixes are no longer used. PHPBB3-11688 --- phpBB/phpbb/cache/driver/file.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'phpBB') diff --git a/phpBB/phpbb/cache/driver/file.php b/phpBB/phpbb/cache/driver/file.php index 7f8c646a11..19596f5205 100644 --- a/phpBB/phpbb/cache/driver/file.php +++ b/phpBB/phpbb/cache/driver/file.php @@ -228,9 +228,7 @@ class phpbb_cache_driver_file extends phpbb_cache_driver_base elseif (strpos($filename, 'container_') === 0 || strpos($filename, 'url_matcher') === 0 || strpos($filename, 'sql_') === 0 || - strpos($filename, 'data_') === 0 || - strpos($filename, 'ctpl_') === 0 || - strpos($filename, 'tpl_') === 0) + strpos($filename, 'data_') === 0) { $this->remove_file($fileInfo->getPathname()); } -- cgit v1.2.1 From a0e5f833113221493540376b9b73718f7a517595 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 14 Jul 2013 15:13:09 -0400 Subject: [ticket/11706] Use @ to suppress errors for getimagesize in remote avatar PHPBB3-11706 --- phpBB/phpbb/avatar/driver/remote.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB') diff --git a/phpBB/phpbb/avatar/driver/remote.php b/phpBB/phpbb/avatar/driver/remote.php index 7da58107a1..d629a490fd 100644 --- a/phpBB/phpbb/avatar/driver/remote.php +++ b/phpBB/phpbb/avatar/driver/remote.php @@ -93,7 +93,7 @@ class phpbb_avatar_driver_remote extends phpbb_avatar_driver // Make sure getimagesize works... if (function_exists('getimagesize')) { - if (($width <= 0 || $height <= 0) && (($image_data = getimagesize($url)) === false)) + if (($width <= 0 || $height <= 0) && (($image_data = @getimagesize($url)) === false)) { $error[] = 'UNABLE_GET_IMAGE_SIZE'; return false; -- cgit v1.2.1 From 8928240dc3fefd42d8e98132451e2de92ff7cbec Mon Sep 17 00:00:00 2001 From: Igor Wiedler Date: Sun, 14 Jul 2013 15:40:09 -0400 Subject: [ticket/11574] Fix more issues in the updater * Stupid mistake in phpbb_create_update_container * Do not bootstrap extensions in installer/updater * Fix template lookup in installer/updater * Do not attempt to delete posts from bots The latter is a really fun problem. Since deleting posts now depends on a new db column that does not exist yet, we cannot call delete_post from a migration, ever. By using retain, we can hack around the issue for now. PHPBB3-11574 --- phpBB/includes/functions_container.php | 12 ++++++------ phpBB/install/index.php | 6 +++++- phpBB/install/install_update.php | 12 ------------ phpBB/phpbb/db/migration/data/30x/3_0_12_rc1.php | 2 +- 4 files changed, 12 insertions(+), 20 deletions(-) (limited to 'phpBB') diff --git a/phpBB/includes/functions_container.php b/phpBB/includes/functions_container.php index 5c6bd6dd8a..7cbfa17a0e 100644 --- a/phpBB/includes/functions_container.php +++ b/phpBB/includes/functions_container.php @@ -148,9 +148,11 @@ function phpbb_create_install_container($phpbb_root_path, $php_ext) */ function phpbb_create_update_container($phpbb_root_path, $php_ext, $config_path) { + $config_file = $phpbb_root_path . 'config.' . $php_ext; return phpbb_create_compiled_container( + $config_file, array( - new phpbb_di_extension_config($phpbb_root_path . 'config.' . $php_ext), + new phpbb_di_extension_config($config_file), new phpbb_di_extension_core($config_path), ), array( @@ -173,11 +175,6 @@ function phpbb_create_update_container($phpbb_root_path, $php_ext, $config_path) */ function phpbb_create_compiled_container($config_file, array $extensions, array $passes, $phpbb_root_path, $php_ext) { - $installed_exts = phpbb_bootstrap_enabled_exts($config_file, $phpbb_root_path); - - // Now pass the enabled extension paths into the ext compiler extension - $extensions[] = new phpbb_di_extension_ext($installed_exts); - // Create the final container to be compiled and cached $container = phpbb_create_container($extensions, $phpbb_root_path, $php_ext); @@ -258,11 +255,14 @@ function phpbb_create_dumped_container_unless_debug($config_file, array $extensi function phpbb_create_default_container($phpbb_root_path, $php_ext) { $config_file = $phpbb_root_path . 'config.' . $php_ext; + $installed_exts = phpbb_bootstrap_enabled_exts($config_file, $phpbb_root_path); + return phpbb_create_dumped_container_unless_debug( $config_file, array( new phpbb_di_extension_config($config_file), new phpbb_di_extension_core($phpbb_root_path . 'config'), + new phpbb_di_extension_ext($installed_exts), ), array( new phpbb_di_pass_collection_pass(), diff --git a/phpBB/install/index.php b/phpBB/install/index.php index ada1f43905..fe61c53558 100644 --- a/phpBB/install/index.php +++ b/phpBB/install/index.php @@ -249,7 +249,11 @@ $phpbb_style_path_provider = new phpbb_style_path_provider(); $template = new phpbb_template_twig($phpbb_root_path, $phpEx, $config, $user, 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', array(), ''); + +$paths = array($phpbb_admin_path . 'style', $phpbb_root_path . 'install/update/new/adm/style'); +$paths = array_filter($paths, 'is_dir'); +$phpbb_style->set_custom_style('admin', $paths, array(), ''); + $template->assign_var('T_ASSETS_PATH', '../assets'); $template->assign_var('T_TEMPLATE_PATH', $phpbb_admin_path . 'style'); diff --git a/phpBB/install/install_update.php b/phpBB/install/install_update.php index f9dfaaef50..478cc9f76f 100644 --- a/phpBB/install/install_update.php +++ b/phpBB/install/install_update.php @@ -222,12 +222,6 @@ class install_update extends module if ($this->test_update === false) { - // Got the updater template itself updated? If so, we are able to directly use it - but only if all three files are present - if (in_array($phpbb_adm_relative_path . 'style/install_update.html', $this->update_info['files'])) - { - $this->tpl_name = '../../install/update/new/adm/style/install_update'; - } - // What about the language file? Got it updated? if (in_array('language/en/install.' . $phpEx, $this->update_info['files'])) { @@ -1068,12 +1062,6 @@ class install_update extends module $this->tpl_name = 'install_update_diff'; - // Got the diff template itself updated? If so, we are able to directly use it - if (in_array($phpbb_adm_relative_path . 'style/install_update_diff.html', $this->update_info['files'])) - { - $this->tpl_name = '../../install/update/new/adm/style/install_update_diff'; - } - $this->page_title = 'VIEWING_FILE_DIFF'; $status = request_var('status', ''); diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_12_rc1.php b/phpBB/phpbb/db/migration/data/30x/3_0_12_rc1.php index 6a31a51201..a89a409dfd 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_12_rc1.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_12_rc1.php @@ -108,7 +108,7 @@ class phpbb_db_migration_data_30x_3_0_12_rc1 extends phpbb_db_migration WHERE user_id = $bot_user_id"; $this->sql_query($sql); - user_delete('remove', $bot_user_id); + user_delete('retain', $bot_user_id); } else { -- cgit v1.2.1 From f96f2a9e23b41106c6a8ed71ad3538141c648c2f Mon Sep 17 00:00:00 2001 From: Bruno Ais Date: Mon, 15 Jul 2013 20:06:54 +0100 Subject: [ticket/11639] generate_text_for_display on functions_posting.php sub-task of ticket PHPBB3-11635: find and fix all bypasses of generate_text_for_* PHPBB3-11639 --- phpBB/includes/functions_posting.php | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) (limited to 'phpBB') diff --git a/phpBB/includes/functions_posting.php b/phpBB/includes/functions_posting.php index b9b518ad32..d277ef06a3 100644 --- a/phpBB/includes/functions_posting.php +++ b/phpBB/includes/functions_posting.php @@ -1104,14 +1104,12 @@ function topic_review($topic_id, $forum_id, $mode = 'topic_review', $cur_post_id $decoded_message = bbcode_nl2br($decoded_message); } - - if ($row['bbcode_bitfield']) - { - $bbcode->bbcode_second_pass($message, $row['bbcode_uid'], $row['bbcode_bitfield']); - } - - $message = bbcode_nl2br($message); - $message = smiley_text($message, !$row['enable_smilies']); + $parse_flags = ($row['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0); + $parse_flags |= ($row['enable_smilies'] ? OPTION_FLAG_SMILIES : 0); + + $message = generate_text_for_display($message, $row['bbcode_uid'], $row['bbcode_bitfield'], $parse_flags , false); + + unset($parse_flags); if (!empty($attachments[$row['post_id']])) { -- cgit v1.2.1 From dde9a1fb27e6db3c1b4cd41d8848496a3ef8d363 Mon Sep 17 00:00:00 2001 From: Bruno Ais Date: Mon, 15 Jul 2013 20:08:17 +0100 Subject: [ticket/11639] Added an useful comment. sub-task of ticket PHPBB3-11635: find and fix all bypasses of generate_text_for_* PHPBB3-11639 --- phpBB/includes/functions_posting.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'phpBB') diff --git a/phpBB/includes/functions_posting.php b/phpBB/includes/functions_posting.php index d277ef06a3..49fbe92256 100644 --- a/phpBB/includes/functions_posting.php +++ b/phpBB/includes/functions_posting.php @@ -1104,9 +1104,10 @@ function topic_review($topic_id, $forum_id, $mode = 'topic_review', $cur_post_id $decoded_message = bbcode_nl2br($decoded_message); } + $parse_flags = ($row['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0); $parse_flags |= ($row['enable_smilies'] ? OPTION_FLAG_SMILIES : 0); - + // Do not censor text because it has already been censored before $message = generate_text_for_display($message, $row['bbcode_uid'], $row['bbcode_bitfield'], $parse_flags , false); unset($parse_flags); -- cgit v1.2.1 From 5f19ca6a6f3ad6641954c58c44ef0c94d1609e5a Mon Sep 17 00:00:00 2001 From: Bruno Ais Date: Mon, 15 Jul 2013 20:09:59 +0100 Subject: [ticket/11639] Whitespace fixing sub-task of ticket PHPBB3-11635: find and fix all bypasses of generate_text_for_* PHPBB3-11639 --- phpBB/includes/functions_posting.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'phpBB') diff --git a/phpBB/includes/functions_posting.php b/phpBB/includes/functions_posting.php index 49fbe92256..ad75ed1079 100644 --- a/phpBB/includes/functions_posting.php +++ b/phpBB/includes/functions_posting.php @@ -1104,12 +1104,12 @@ function topic_review($topic_id, $forum_id, $mode = 'topic_review', $cur_post_id $decoded_message = bbcode_nl2br($decoded_message); } - + $parse_flags = ($row['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0); $parse_flags |= ($row['enable_smilies'] ? OPTION_FLAG_SMILIES : 0); // Do not censor text because it has already been censored before $message = generate_text_for_display($message, $row['bbcode_uid'], $row['bbcode_bitfield'], $parse_flags , false); - + unset($parse_flags); if (!empty($attachments[$row['post_id']])) -- cgit v1.2.1 From 0759b606c25a6ae38ab4c7eb35ebcc2b01e3f5eb Mon Sep 17 00:00:00 2001 From: Vjacheslav Trushkin Date: Tue, 16 Jul 2013 20:11:28 +0300 Subject: [ticket/11708] Fix bulletin points in notifications PHPBB3-11708 --- phpBB/styles/prosilver/theme/common.css | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'phpBB') diff --git a/phpBB/styles/prosilver/theme/common.css b/phpBB/styles/prosilver/theme/common.css index e58386de45..a2b8034187 100644 --- a/phpBB/styles/prosilver/theme/common.css +++ b/phpBB/styles/prosilver/theme/common.css @@ -758,6 +758,10 @@ p.rules a { clear: both; } +#notification_list ul li:before, #notification_list ul li:after { + display: none; +} + #notification_list > .header { padding: 0 10px; font-family: Arial, "Helvetica Neue", Helvetica, Arial, sans-serif; -- cgit v1.2.1 From 792c730f15ef61d444dcdbcee91831ec89366a88 Mon Sep 17 00:00:00 2001 From: Andreas Fischer Date: Tue, 16 Jul 2013 20:11:58 +0200 Subject: [ticket/10931] Add phpbb_php_ini as a service. PHPBB3-10931 --- phpBB/config/services.yml | 3 +++ 1 file changed, 3 insertions(+) (limited to 'phpBB') diff --git a/phpBB/config/services.yml b/phpBB/config/services.yml index c1579cfb57..6d30a154e2 100644 --- a/phpBB/config/services.yml +++ b/phpBB/config/services.yml @@ -239,6 +239,9 @@ services: - %tables.notifications% - %tables.user_notifications% + php_ini: + class: phpbb_php_ini + request: class: phpbb_request -- cgit v1.2.1 From fc6bed28566590c26fab5845a6b94cf9b795e4da Mon Sep 17 00:00:00 2001 From: Bruno Ais Date: Tue, 16 Jul 2013 20:25:08 +0100 Subject: [ticket/11640] generate_text_for_display on functions_privmsgs.php sub-task of ticket PHPBB3-11635: find and fix all bypasses of generate_text_for_* PHPBB3-11640 --- phpBB/includes/functions_privmsgs.php | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) (limited to 'phpBB') diff --git a/phpBB/includes/functions_privmsgs.php b/phpBB/includes/functions_privmsgs.php index 14278a2529..001cf7bba0 100644 --- a/phpBB/includes/functions_privmsgs.php +++ b/phpBB/includes/functions_privmsgs.php @@ -2018,14 +2018,12 @@ function message_history($msg_id, $user_id, $message_row, $folder, $in_post_mode $decoded_message = bbcode_nl2br($decoded_message); } - - if ($row['bbcode_bitfield']) - { - $bbcode->bbcode_second_pass($message, $row['bbcode_uid'], $row['bbcode_bitfield']); - } - - $message = bbcode_nl2br($message); - $message = smiley_text($message, !$row['enable_smilies']); + + $parse_flags = ($row['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0); + $parse_flags |= ($row['enable_smilies'] ? OPTION_FLAG_SMILIES : 0); + + $message = generate_text_for_display($message, $row['bbcode_uid'], $row['bbcode_bitfield'], $parse_flags , false); + unset($parse_flags); $subject = censor_text($subject); -- cgit v1.2.1 From e1e8d4ed347cb1707ee4cfca8d05e679b575fe0c Mon Sep 17 00:00:00 2001 From: Bruno Ais Date: Tue, 16 Jul 2013 21:01:47 +0100 Subject: [ticket/11641] generate_text_for_display on mcp/mcp_pm_reports.php sub-task of ticket PHPBB3-11635: find and fix all bypasses of generate_text_for_* PHPBB3-11641 --- phpBB/includes/mcp/mcp_pm_reports.php | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) (limited to 'phpBB') diff --git a/phpBB/includes/mcp/mcp_pm_reports.php b/phpBB/includes/mcp/mcp_pm_reports.php index 99ff397a66..dc953aae33 100644 --- a/phpBB/includes/mcp/mcp_pm_reports.php +++ b/phpBB/includes/mcp/mcp_pm_reports.php @@ -115,17 +115,8 @@ class mcp_pm_reports } // Process message, leave it uncensored - $message = $pm_info['message_text']; + $message = generate_text_for_display($pm_info['message_text'], $pm_info['bbcode_uid'], $pm_info['bbcode_bitfield'], ($pm_info['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES, false); - if ($pm_info['bbcode_bitfield']) - { - include_once($phpbb_root_path . 'includes/bbcode.' . $phpEx); - $bbcode = new bbcode($pm_info['bbcode_bitfield']); - $bbcode->bbcode_second_pass($message, $pm_info['bbcode_uid'], $pm_info['bbcode_bitfield']); - } - - $message = bbcode_nl2br($message); - $message = smiley_text($message); $report['report_text'] = make_clickable(bbcode_nl2br($report['report_text'])); if ($pm_info['message_attachment'] && $auth->acl_get('u_pm_download')) -- cgit v1.2.1 From e7bf3abd1ac79fabab7da925e55bd884aee0663d Mon Sep 17 00:00:00 2001 From: Bruno Ais Date: Tue, 16 Jul 2013 21:15:59 +0100 Subject: [ticket/11642] generate_text_for_display on mcp/mcp_post.php sub-task of ticket PHPBB3-11635: find and fix all bypasses of generate_text_for_* PHPBB3-11642 --- phpBB/includes/mcp/mcp_post.php | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) (limited to 'phpBB') diff --git a/phpBB/includes/mcp/mcp_post.php b/phpBB/includes/mcp/mcp_post.php index 520c964228..235b2a44be 100644 --- a/phpBB/includes/mcp/mcp_post.php +++ b/phpBB/includes/mcp/mcp_post.php @@ -125,17 +125,7 @@ function mcp_post_details($id, $mode, $action) $post_unread = (isset($topic_tracking_info[$post_info['topic_id']]) && $post_info['post_time'] > $topic_tracking_info[$post_info['topic_id']]) ? true : false; // Process message, leave it uncensored - $message = $post_info['post_text']; - - if ($post_info['bbcode_bitfield']) - { - include_once($phpbb_root_path . 'includes/bbcode.' . $phpEx); - $bbcode = new bbcode($post_info['bbcode_bitfield']); - $bbcode->bbcode_second_pass($message, $post_info['bbcode_uid'], $post_info['bbcode_bitfield']); - } - - $message = bbcode_nl2br($message); - $message = smiley_text($message); + $message = generate_text_for_display($post_info['message_text'], $post_info['bbcode_uid'], $post_info['bbcode_bitfield'], ($post_info['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES, false); if ($post_info['post_attachment'] && $auth->acl_get('u_download') && $auth->acl_get('f_download', $post_info['forum_id'])) { -- cgit v1.2.1 From 596e9bb69df2f5d0c07c0b8201cc770bbe5253a0 Mon Sep 17 00:00:00 2001 From: Bruno Ais Date: Tue, 16 Jul 2013 21:20:22 +0100 Subject: [ticket/11643] generate_text_for_display on mcp/mcp_queue.php sub-task of ticket PHPBB3-11635: find and fix all bypasses of generate_text_for_* PHPBB3-11643 --- phpBB/includes/mcp/mcp_queue.php | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) (limited to 'phpBB') diff --git a/phpBB/includes/mcp/mcp_queue.php b/phpBB/includes/mcp/mcp_queue.php index 24afa1f210..14490343c2 100644 --- a/phpBB/includes/mcp/mcp_queue.php +++ b/phpBB/includes/mcp/mcp_queue.php @@ -132,17 +132,7 @@ class mcp_queue $post_unread = (isset($topic_tracking_info[$post_info['topic_id']]) && $post_info['post_time'] > $topic_tracking_info[$post_info['topic_id']]) ? true : false; // Process message, leave it uncensored - $message = $post_info['post_text']; - - if ($post_info['bbcode_bitfield']) - { - include_once($phpbb_root_path . 'includes/bbcode.' . $phpEx); - $bbcode = new bbcode($post_info['bbcode_bitfield']); - $bbcode->bbcode_second_pass($message, $post_info['bbcode_uid'], $post_info['bbcode_bitfield']); - } - - $message = bbcode_nl2br($message); - $message = smiley_text($message); + $message = generate_text_for_display($post_info['message_text'], $post_info['bbcode_uid'], $post_info['bbcode_bitfield'], ($post_info['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES, false); if ($post_info['post_attachment'] && $auth->acl_get('u_download') && $auth->acl_get('f_download', $post_info['forum_id'])) { -- cgit v1.2.1 From d183431894b85ca2ebc778ccb8fd52ecf91082fb Mon Sep 17 00:00:00 2001 From: Bruno Ais Date: Tue, 16 Jul 2013 21:28:06 +0100 Subject: [ticket/11653] generate_text_for_display on mcp/mcp_topic.php sub-task of ticket PHPBB3-11635: find and fix all bypasses of generate_text_for_* PHPBB3-11653 --- phpBB/includes/mcp/mcp_topic.php | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) (limited to 'phpBB') diff --git a/phpBB/includes/mcp/mcp_topic.php b/phpBB/includes/mcp/mcp_topic.php index e3dd5a6b57..3491f37bcb 100644 --- a/phpBB/includes/mcp/mcp_topic.php +++ b/phpBB/includes/mcp/mcp_topic.php @@ -213,13 +213,7 @@ function mcp_topic_view($id, $mode, $action) $message = $row['post_text']; $post_subject = ($row['post_subject'] != '') ? $row['post_subject'] : $topic_info['topic_title']; - if ($row['bbcode_bitfield']) - { - $bbcode->bbcode_second_pass($message, $row['bbcode_uid'], $row['bbcode_bitfield']); - } - - $message = bbcode_nl2br($message); - $message = smiley_text($message); + $message = generate_text_for_display($message, $row['bbcode_uid'], $row['bbcode_bitfield'], ($row['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES, false); if (!empty($attachments[$row['post_id']])) { -- cgit v1.2.1 From 16b411616575cdd4023fb42bb77b56e43db735e0 Mon Sep 17 00:00:00 2001 From: Bruno Ais Date: Thu, 18 Jul 2013 16:15:36 +0100 Subject: [ticket/11654] generate_text_for_display on mcp/mcp_warn.php sub-task of ticket PHPBB3-11635: find and fix all bypasses of generate_text_for_* PHPBB3-11654 --- phpBB/includes/mcp/mcp_warn.php | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) (limited to 'phpBB') diff --git a/phpBB/includes/mcp/mcp_warn.php b/phpBB/includes/mcp/mcp_warn.php index 4ef477775d..d0fcd8a77d 100644 --- a/phpBB/includes/mcp/mcp_warn.php +++ b/phpBB/includes/mcp/mcp_warn.php @@ -289,19 +289,7 @@ class mcp_warn // We want to make the message available here as a reminder // Parse the message and subject - $message = censor_text($user_row['post_text']); - - // Second parse bbcode here - if ($user_row['bbcode_bitfield']) - { - include_once($phpbb_root_path . 'includes/bbcode.' . $phpEx); - - $bbcode = new bbcode($user_row['bbcode_bitfield']); - $bbcode->bbcode_second_pass($message, $user_row['bbcode_uid'], $user_row['bbcode_bitfield']); - } - - $message = bbcode_nl2br($message); - $message = smiley_text($message); + $message = generate_text_for_display($message, $user_row['bbcode_uid'], $user_row['bbcode_bitfield'], ($user_row['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES, true); // Generate the appropriate user information for the user we are looking at if (!function_exists('phpbb_get_user_avatar')) -- cgit v1.2.1 From d05c04ae40326768e35464d42e3bd9e51b140155 Mon Sep 17 00:00:00 2001 From: Vjacheslav Trushkin Date: Thu, 18 Jul 2013 19:10:36 +0300 Subject: [ticket/11712] Fixing typo in editor.js PHPBB3-11712 --- phpBB/styles/prosilver/template/editor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB') diff --git a/phpBB/styles/prosilver/template/editor.js b/phpBB/styles/prosilver/template/editor.js index 235cc0025b..4c70ee345f 100644 --- a/phpBB/styles/prosilver/template/editor.js +++ b/phpBB/styles/prosilver/template/editor.js @@ -301,7 +301,7 @@ function colorPalette(dir, width, height) { var r = 0, g = 0, b = 0, - numberList = new Array(6); + numberList = new Array(6), color = '', html = ''; -- cgit v1.2.1 From f421c082f73b26f5578d14af7cdbfefd013f554a Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Thu, 18 Jul 2013 22:41:23 +0200 Subject: [ticket/11713] Do not remove module if it couldn't be deleted Up to now, the module or module category was always removed with jQuery, even if there was an error. With this change, the modules will not be deleted by jQuery if the return JSON array will have SUCCESS set to false. PHPBB3-11713 --- phpBB/adm/style/ajax.js | 6 ++++-- phpBB/includes/acp/acp_modules.php | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) (limited to 'phpBB') diff --git a/phpBB/adm/style/ajax.js b/phpBB/adm/style/ajax.js index 6f21dfa6ac..efb0639f1b 100644 --- a/phpBB/adm/style/ajax.js +++ b/phpBB/adm/style/ajax.js @@ -127,8 +127,10 @@ phpbb.addAjaxCallback('activate_deactivate', function(res) { * The removes the parent row of the link or form that triggered the callback, * and is good for stuff like the removal of forums. */ -phpbb.addAjaxCallback('row_delete', function() { - $(this).parents('tr').remove(); +phpbb.addAjaxCallback('row_delete', function(res) { + if (res.SUCCESS !== false) { + $(this).parents('tr').remove(); + } }); diff --git a/phpBB/includes/acp/acp_modules.php b/phpBB/includes/acp/acp_modules.php index a1e681b29c..7a1d30196d 100644 --- a/phpBB/includes/acp/acp_modules.php +++ b/phpBB/includes/acp/acp_modules.php @@ -379,6 +379,7 @@ class acp_modules $json_response->send(array( 'MESSAGE_TITLE' => $user->lang('ERROR'), 'MESSAGE_TEXT' => implode('
', $errors), + 'SUCCESS' => false, )); } -- cgit v1.2.1 From ef7a7cac6dc3f313960a70462b084fbeaff9d4bd Mon Sep 17 00:00:00 2001 From: Bruno Ais Date: Fri, 19 Jul 2013 18:27:25 +0100 Subject: [ticket/11655] generate_text_for_display on ucp_pm_viewmessage.php sub-task of ticket PHPBB3-11635: find and fix all bypasses of generate_text_for_* PHPBB3-11655 --- phpBB/includes/ucp/ucp_pm_viewmessage.php | 28 ++-------------------------- 1 file changed, 2 insertions(+), 26 deletions(-) (limited to 'phpBB') diff --git a/phpBB/includes/ucp/ucp_pm_viewmessage.php b/phpBB/includes/ucp/ucp_pm_viewmessage.php index b7d2dd6821..0a8a3d55ab 100644 --- a/phpBB/includes/ucp/ucp_pm_viewmessage.php +++ b/phpBB/includes/ucp/ucp_pm_viewmessage.php @@ -76,17 +76,7 @@ function view_message($id, $mode, $folder_id, $msg_id, $folder, $message_row) $user_info = get_user_information($author_id, $message_row); // Parse the message and subject - $message = censor_text($message_row['message_text']); - - // Second parse bbcode here - if ($message_row['bbcode_bitfield']) - { - $bbcode->bbcode_second_pass($message, $message_row['bbcode_uid'], $message_row['bbcode_bitfield']); - } - - // Always process smilies after parsing bbcodes - $message = bbcode_nl2br($message); - $message = smiley_text($message); + $message = generate_text_for_display($message_row['message_text'], $message_row['bbcode_uid'], $message_row['bbcode_bitfield'], ($message_row['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES, true); // Replace naughty words such as farty pants $message_row['message_subject'] = censor_text($message_row['message_subject']); @@ -160,21 +150,7 @@ function view_message($id, $mode, $folder_id, $msg_id, $folder, $message_row) // End signature parsing, only if needed if ($signature) { - $signature = censor_text($signature); - - if ($user_info['user_sig_bbcode_bitfield']) - { - if ($bbcode === false) - { - include($phpbb_root_path . 'includes/bbcode.' . $phpEx); - $bbcode = new bbcode($user_info['user_sig_bbcode_bitfield']); - } - - $bbcode->bbcode_second_pass($signature, $user_info['user_sig_bbcode_uid'], $user_info['user_sig_bbcode_bitfield']); - } - - $signature = bbcode_nl2br($signature); - $signature = smiley_text($signature); + $signature = generate_text_for_display($signature, $user_info['bbcode_uid'], $user_info['bbcode_bitfield'], ($user_info['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES, true); } $url = append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm'); -- cgit v1.2.1 From b92f660ed389414a1d8550a5ba92804f7151eb79 Mon Sep 17 00:00:00 2001 From: Nathaniel Guse Date: Fri, 19 Jul 2013 13:08:41 -0500 Subject: [ticket/11718] Twig lexer only correcting statements in IF, not ELSEIF PHPBB3-11718 --- phpBB/phpbb/template/twig/lexer.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'phpBB') diff --git a/phpBB/phpbb/template/twig/lexer.php b/phpBB/phpbb/template/twig/lexer.php index 46412ad048..cb44de76f1 100644 --- a/phpBB/phpbb/template/twig/lexer.php +++ b/phpBB/phpbb/template/twig/lexer.php @@ -219,19 +219,20 @@ class phpbb_template_twig_lexer extends Twig_Lexer { $callback = function($matches) { + $inner = $matches[2]; // Replace $TEST with definition.TEST - $matches[1] = preg_replace('#\s\$([a-zA-Z_0-9]+)#', ' definition.$1', $matches[1]); + $inner = preg_replace('#\s\$([a-zA-Z_0-9]+)#', ' definition.$1', $inner); // Replace .test with test|length - $matches[1] = preg_replace('#\s\.([a-zA-Z_0-9\.]+)#', ' $1|length', $matches[1]); + $inner = preg_replace('#\s\.([a-zA-Z_0-9\.]+)#', ' $1|length', $inner); - return ''; + return ""; }; // Replace our "div by" with Twig's divisibleby (Twig does not like test names with spaces) $code = preg_replace('# div by ([0-9]+)#', ' divisibleby($1)', $code); - return preg_replace_callback('##', $callback, $code); + return preg_replace_callback('##', $callback, $code); } /** -- cgit v1.2.1 From 375976eb38882f42155f1f7fa2c849add8dbcf08 Mon Sep 17 00:00:00 2001 From: Nathaniel Guse Date: Fri, 19 Jul 2013 14:12:28 -0500 Subject: [ticket/11707] Twig DEFINE not working as expected PHPBB3-11707 --- phpBB/phpbb/template/twig/lexer.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'phpBB') diff --git a/phpBB/phpbb/template/twig/lexer.php b/phpBB/phpbb/template/twig/lexer.php index 46412ad048..1fa4c5b3e6 100644 --- a/phpBB/phpbb/template/twig/lexer.php +++ b/phpBB/phpbb/template/twig/lexer.php @@ -126,10 +126,14 @@ class phpbb_template_twig_lexer extends Twig_Lexer { $callback = function($matches) { - // Remove any quotes that may have been used in different implementations - // E.g. DEFINE $TEST = 'blah' vs INCLUDE foo - // Replace {} with start/end to parse variables (' ~ TEST ~ '.html) - $matches[2] = str_replace(array('"', "'", '{', '}'), array('', '', "' ~ ", " ~ '"), $matches[2]); + // Remove matching quotes at the beginning/end if a statement; + // E.g. 'asdf'"' -> asdf'" + // E.g. "asdf'"" -> asdf'" + // E.g. 'asdf'" -> 'asdf'" + $matches[2] = preg_replace('#^([\'"])?(.+?)\1$#', '$2', $matches[2]); + + // Replace template variables with start/end to parse variables (' ~ TEST ~ '.html) + $matches[2] = preg_replace('#{([a-zA-Z0-9_\.$]+)}#', "'~ \$1 ~'", $matches[2]); // Surround the matches in single quotes ('' ~ TEST ~ '.html') return ""; -- cgit v1.2.1 From b3ad2fc23f35fce2a888bb8f9c35ece247e0bc09 Mon Sep 17 00:00:00 2001 From: Bruno Ais Date: Sat, 20 Jul 2013 16:16:10 +0100 Subject: [ticket/11642] Fixed typo in the variable name. sub-task of ticket PHPBB3-11635: find and fix all bypasses of generate_text_for_* PHPBB3-11642 --- phpBB/includes/mcp/mcp_post.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB') diff --git a/phpBB/includes/mcp/mcp_post.php b/phpBB/includes/mcp/mcp_post.php index 235b2a44be..e8768957e0 100644 --- a/phpBB/includes/mcp/mcp_post.php +++ b/phpBB/includes/mcp/mcp_post.php @@ -125,7 +125,7 @@ function mcp_post_details($id, $mode, $action) $post_unread = (isset($topic_tracking_info[$post_info['topic_id']]) && $post_info['post_time'] > $topic_tracking_info[$post_info['topic_id']]) ? true : false; // Process message, leave it uncensored - $message = generate_text_for_display($post_info['message_text'], $post_info['bbcode_uid'], $post_info['bbcode_bitfield'], ($post_info['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES, false); + $message = generate_text_for_display($post_info['post_text'], $post_info['bbcode_uid'], $post_info['bbcode_bitfield'], ($post_info['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES, false); if ($post_info['post_attachment'] && $auth->acl_get('u_download') && $auth->acl_get('f_download', $post_info['forum_id'])) { -- cgit v1.2.1 From f1bfbde3f5bdb9191057f28dd623dc2a3a530bf7 Mon Sep 17 00:00:00 2001 From: Bruno Ais Date: Sat, 20 Jul 2013 16:19:27 +0100 Subject: [ticket/11643] Fixed typo in the variable name. sub-task of ticket PHPBB3-11635: find and fix all bypasses of generate_text_for_* PHPBB3-11643 --- phpBB/includes/mcp/mcp_queue.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB') diff --git a/phpBB/includes/mcp/mcp_queue.php b/phpBB/includes/mcp/mcp_queue.php index 14490343c2..2c95dc6a67 100644 --- a/phpBB/includes/mcp/mcp_queue.php +++ b/phpBB/includes/mcp/mcp_queue.php @@ -132,7 +132,7 @@ class mcp_queue $post_unread = (isset($topic_tracking_info[$post_info['topic_id']]) && $post_info['post_time'] > $topic_tracking_info[$post_info['topic_id']]) ? true : false; // Process message, leave it uncensored - $message = generate_text_for_display($post_info['message_text'], $post_info['bbcode_uid'], $post_info['bbcode_bitfield'], ($post_info['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES, false); + $message = generate_text_for_display($post_info['post_text'], $post_info['bbcode_uid'], $post_info['bbcode_bitfield'], ($post_info['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES, false); if ($post_info['post_attachment'] && $auth->acl_get('u_download') && $auth->acl_get('f_download', $post_info['forum_id'])) { -- cgit v1.2.1 From fc64e6997f9d54362a7fbe2f7a366fb8ba497deb Mon Sep 17 00:00:00 2001 From: Bruno Ais Date: Sat, 20 Jul 2013 16:24:31 +0100 Subject: [ticket/11638] Fixed typo in the variable name. sub-task of ticket PHPBB3-11635: find and fix all bypasses of generate_text_for_* PHPBB3-11638 --- phpBB/viewtopic.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB') diff --git a/phpBB/viewtopic.php b/phpBB/viewtopic.php index 9274539ab4..d18478fbfa 100644 --- a/phpBB/viewtopic.php +++ b/phpBB/viewtopic.php @@ -1385,7 +1385,7 @@ for ($i = 0, $end = sizeof($post_list); $i < $end; ++$i) } // Parse the message and subject - $message = generate_text_for_display($message, $row['bbcode_uid'], $row['bbcode_bitfield'], ($row['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES, true); + $message = generate_text_for_display($row['post_text'], $row['bbcode_uid'], $row['bbcode_bitfield'], ($row['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES, true); if (!empty($attachments[$row['post_id']])) { -- cgit v1.2.1 From 73414823048cac8c2963b2034ba13daaf60c3fee Mon Sep 17 00:00:00 2001 From: Bruno Ais Date: Sat, 20 Jul 2013 16:25:05 +0100 Subject: [ticket/11638] Fixed not following guidelines for brackets sub-task of ticket PHPBB3-11635: find and fix all bypasses of generate_text_for_* PHPBB3-11638 --- phpBB/viewtopic.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'phpBB') diff --git a/phpBB/viewtopic.php b/phpBB/viewtopic.php index d18478fbfa..de76d1186d 100644 --- a/phpBB/viewtopic.php +++ b/phpBB/viewtopic.php @@ -830,7 +830,8 @@ if (!empty($topic_data['poll_start'])) $parse_bbcode_flags = OPTION_FLAG_SMILIES; - if(empty($poll_info[0]['bbcode_bitfield'])){ + if(empty($poll_info[0]['bbcode_bitfield'])) + { $parse_bbcode_flags |= OPTION_FLAG_BBCODE; } -- cgit v1.2.1 From 0ef1bcac2b3152bbf389b512fd373987a7d0edce Mon Sep 17 00:00:00 2001 From: Bruno Ais Date: Sat, 20 Jul 2013 16:31:08 +0100 Subject: [ticket/11639] Whitespace fixing sub-task of ticket PHPBB3-11635: find and fix all bypasses of generate_text_for_* PHPBB3-11639 --- phpBB/includes/functions_posting.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB') diff --git a/phpBB/includes/functions_posting.php b/phpBB/includes/functions_posting.php index ad75ed1079..49a1797321 100644 --- a/phpBB/includes/functions_posting.php +++ b/phpBB/includes/functions_posting.php @@ -1108,7 +1108,7 @@ function topic_review($topic_id, $forum_id, $mode = 'topic_review', $cur_post_id $parse_flags = ($row['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0); $parse_flags |= ($row['enable_smilies'] ? OPTION_FLAG_SMILIES : 0); // Do not censor text because it has already been censored before - $message = generate_text_for_display($message, $row['bbcode_uid'], $row['bbcode_bitfield'], $parse_flags , false); + $message = generate_text_for_display($message, $row['bbcode_uid'], $row['bbcode_bitfield'], $parse_flags, false); unset($parse_flags); -- cgit v1.2.1 From 67ba959d9b34ff727b77206f4c706b1fbe024cb2 Mon Sep 17 00:00:00 2001 From: Bruno Ais Date: Sat, 20 Jul 2013 16:35:28 +0100 Subject: [ticket/11654] first parameter fail sub-task of ticket PHPBB3-11635: find and fix all bypasses of generate_text_for_* PHPBB3-11654 --- phpBB/includes/mcp/mcp_warn.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB') diff --git a/phpBB/includes/mcp/mcp_warn.php b/phpBB/includes/mcp/mcp_warn.php index d0fcd8a77d..65cf641418 100644 --- a/phpBB/includes/mcp/mcp_warn.php +++ b/phpBB/includes/mcp/mcp_warn.php @@ -289,7 +289,7 @@ class mcp_warn // We want to make the message available here as a reminder // Parse the message and subject - $message = generate_text_for_display($message, $user_row['bbcode_uid'], $user_row['bbcode_bitfield'], ($user_row['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES, true); + $message = generate_text_for_display($user_row['post_text'], $user_row['bbcode_uid'], $user_row['bbcode_bitfield'], ($user_row['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES, true); // Generate the appropriate user information for the user we are looking at if (!function_exists('phpbb_get_user_avatar')) -- cgit v1.2.1 From 43b172c8aabbfbcc5180a3f3ad5daede45fcc041 Mon Sep 17 00:00:00 2001 From: Bruno Ais Date: Sat, 20 Jul 2013 16:44:24 +0100 Subject: [ticket/11655] wrong var names for the uid and for the bitfield sub-task of ticket PHPBB3-11635: find and fix all bypasses of generate_text_for_* PHPBB3-11655 --- phpBB/includes/ucp/ucp_pm_viewmessage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB') diff --git a/phpBB/includes/ucp/ucp_pm_viewmessage.php b/phpBB/includes/ucp/ucp_pm_viewmessage.php index 0a8a3d55ab..52a28e3552 100644 --- a/phpBB/includes/ucp/ucp_pm_viewmessage.php +++ b/phpBB/includes/ucp/ucp_pm_viewmessage.php @@ -150,7 +150,7 @@ function view_message($id, $mode, $folder_id, $msg_id, $folder, $message_row) // End signature parsing, only if needed if ($signature) { - $signature = generate_text_for_display($signature, $user_info['bbcode_uid'], $user_info['bbcode_bitfield'], ($user_info['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES, true); + $signature = generate_text_for_display($signature, $user_info['user_sig_bbcode_uid'], $user_info['user_sig_bbcode_bitfield'], ($user_info['user_sig_bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES, true); } $url = append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm'); -- cgit v1.2.1 From 603dc1f78617e64e41f61daf85f463b0465123ec Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 20 Jul 2013 15:09:28 +0200 Subject: [ticket/11717] Use topic_posts_approved instead of topic_replies Due to the move to soft-delete, the topic_replies column no longer exists in the topics table. Instead, the column topic_posts_approved should be used. PHPBB3-11717 --- phpBB/includes/functions_posting.php | 5 ++++- phpBB/includes/ucp/ucp_prefs.php | 2 +- phpBB/phpbb/feed/topic.php | 2 +- phpBB/search.php | 4 ++-- 4 files changed, 8 insertions(+), 5 deletions(-) (limited to 'phpBB') diff --git a/phpBB/includes/functions_posting.php b/phpBB/includes/functions_posting.php index 03565c27bb..f80736a6c4 100644 --- a/phpBB/includes/functions_posting.php +++ b/phpBB/includes/functions_posting.php @@ -1995,6 +1995,9 @@ function submit_post($mode, $subject, $username, $topic_type, &$poll, &$data, $u } } + $first_post_topic_info = ($post_mode == 'edit_first_post' && (($post_visibility == ITEM_DELETED && $data['topic_posts_softdeleted'] == 1) || + ($post_visibility == ITEM_UNAPPROVED && $data['topic_posts_unapproved'] == 1) || + ($post_visibility == ITEM_APPROVED && $data['topic_posts_approved'] == 1))); // Fix the post's and topic's visibility and first/last post information, when the post is edited if (($post_mode != 'post' && $post_mode != 'reply') && $data['post_visibility'] != $post_visibility) { @@ -2007,7 +2010,7 @@ function submit_post($mode, $subject, $username, $topic_type, &$poll, &$data, $u $phpbb_content_visibility = $phpbb_container->get('content.visibility'); $phpbb_content_visibility->set_post_visibility($post_visibility, $data['post_id'], $data['topic_id'], $data['forum_id'], $user->data['user_id'], time(), '', $is_starter, $is_latest); } - else if ($post_mode == 'edit_last_post' || $post_mode == 'edit_topic' || ($post_mode == 'edit_first_post' && !$data['topic_replies'])) + else if ($post_mode == 'edit_last_post' || $post_mode == 'edit_topic' || $first_post_topic_info) { if ($post_visibility == ITEM_APPROVED || $data['topic_visibility'] == $post_visibility) { diff --git a/phpBB/includes/ucp/ucp_prefs.php b/phpBB/includes/ucp/ucp_prefs.php index 7c3286c1d1..f24578da84 100644 --- a/phpBB/includes/ucp/ucp_prefs.php +++ b/phpBB/includes/ucp/ucp_prefs.php @@ -267,7 +267,7 @@ class ucp_prefs $limit_topic_days = array(0 => $user->lang['ALL_TOPICS'], 1 => $user->lang['1_DAY'], 7 => $user->lang['7_DAYS'], 14 => $user->lang['2_WEEKS'], 30 => $user->lang['1_MONTH'], 90 => $user->lang['3_MONTHS'], 180 => $user->lang['6_MONTHS'], 365 => $user->lang['1_YEAR']); $sort_by_topic_text = array('a' => $user->lang['AUTHOR'], 't' => $user->lang['POST_TIME'], 'r' => $user->lang['REPLIES'], 's' => $user->lang['SUBJECT'], 'v' => $user->lang['VIEWS']); - $sort_by_topic_sql = array('a' => 't.topic_first_poster_name', 't' => 't.topic_last_post_time', 'r' => 't.topic_replies', 's' => 't.topic_title', 'v' => 't.topic_views'); + $sort_by_topic_sql = array('a' => 't.topic_first_poster_name', 't' => 't.topic_last_post_time', 'r' => 't.topic_posts_approved', 's' => 't.topic_title', 'v' => 't.topic_views'); // Post ordering options $limit_post_days = array(0 => $user->lang['ALL_POSTS'], 1 => $user->lang['1_DAY'], 7 => $user->lang['7_DAYS'], 14 => $user->lang['2_WEEKS'], 30 => $user->lang['1_MONTH'], 90 => $user->lang['3_MONTHS'], 180 => $user->lang['6_MONTHS'], 365 => $user->lang['1_YEAR']); diff --git a/phpBB/phpbb/feed/topic.php b/phpBB/phpbb/feed/topic.php index 36f958ac60..696b0f5a52 100644 --- a/phpBB/phpbb/feed/topic.php +++ b/phpBB/phpbb/feed/topic.php @@ -43,7 +43,7 @@ class phpbb_feed_topic extends phpbb_feed_post_base function open() { - $sql = 'SELECT f.forum_options, f.forum_password, t.topic_id, t.forum_id, t.topic_visibility, t.topic_title, t.topic_time, t.topic_views, t.topic_replies, t.topic_type + $sql = 'SELECT f.forum_options, f.forum_password, t.topic_id, t.forum_id, t.topic_visibility, t.topic_title, t.topic_time, t.topic_views, t.topic_posts_approved, t.topic_type FROM ' . TOPICS_TABLE . ' t LEFT JOIN ' . FORUMS_TABLE . ' f ON (f.forum_id = t.forum_id) diff --git a/phpBB/search.php b/phpBB/search.php index 2429c81dae..8bcbfc498b 100644 --- a/phpBB/search.php +++ b/phpBB/search.php @@ -366,7 +366,7 @@ if ($keywords || $author || $author_id || $search_id || $submit) { $sql = "SELECT p.post_id FROM $sort_join" . POSTS_TABLE . ' p, ' . TOPICS_TABLE . " t - WHERE t.topic_replies = 0 + WHERE t.topic_posts_approved = 1 AND p.topic_id = t.topic_id $last_post_time AND $m_approve_posts_fid_sql @@ -378,7 +378,7 @@ if ($keywords || $author || $author_id || $search_id || $submit) { $sql = 'SELECT DISTINCT ' . $sort_by_sql[$sort_key] . ", p.topic_id FROM $sort_join" . POSTS_TABLE . ' p, ' . TOPICS_TABLE . " t - WHERE t.topic_replies = 0 + WHERE t.topic_posts_approved = 1 AND t.topic_moved_id = 0 AND p.topic_id = t.topic_id $last_post_time -- cgit v1.2.1 From 56df3fd8cafde10b230c925c7eb455003ae76382 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 20 Jul 2013 22:02:51 +0200 Subject: [ticket/11720] Do not call $captcha->validate if $captcha is not set PHPBB3-11566 changed big parts of code. Unfortunately, a call to $captcha->validate was added that is being called even if $captcha hasn't been initialized. This change will fix this issue. PHPBB3-11720 --- phpBB/report.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'phpBB') diff --git a/phpBB/report.php b/phpBB/report.php index c92ecdfdcc..c909b4fcf3 100644 --- a/phpBB/report.php +++ b/phpBB/report.php @@ -146,10 +146,13 @@ $s_hidden_fields = ''; // Submit report? if ($submit && $reason_id) { - $visual_confirmation_response = $captcha->validate(); - if ($visual_confirmation_response) + if (isset($captcha)) { - $error[] = $visual_confirmation_response; + $visual_confirmation_response = $captcha->validate(); + if ($visual_confirmation_response) + { + $error[] = $visual_confirmation_response; + } } $sql = 'SELECT * -- cgit v1.2.1 From 570e21285bab8f98b45dfa46d0c6b6a8de885055 Mon Sep 17 00:00:00 2001 From: Andreas Fischer Date: Mon, 22 Jul 2013 03:30:27 +0200 Subject: [ticket/11722] Remove reference assignment for $captcha in report.php PHPBB3-11722 --- phpBB/report.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB') diff --git a/phpBB/report.php b/phpBB/report.php index 063e55e571..c9ca57ecbe 100644 --- a/phpBB/report.php +++ b/phpBB/report.php @@ -147,7 +147,7 @@ else if ($config['enable_post_confirm'] && !$user->data['is_registered']) { include($phpbb_root_path . 'includes/captcha/captcha_factory.' . $phpEx); - $captcha =& phpbb_captcha_factory::get_instance($config['captcha_plugin']); + $captcha = phpbb_captcha_factory::get_instance($config['captcha_plugin']); $captcha->init(CONFIRM_REPORT); } -- cgit v1.2.1 From effafa4b4d82e8f0debcda2e84e03117433e5a7d Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Mon, 22 Jul 2013 10:42:46 +0200 Subject: [ticket/11717] Add 'has' to boolean variable and reduce line length PHPBB3-11717 --- phpBB/includes/functions_posting.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'phpBB') diff --git a/phpBB/includes/functions_posting.php b/phpBB/includes/functions_posting.php index f80736a6c4..103cc81205 100644 --- a/phpBB/includes/functions_posting.php +++ b/phpBB/includes/functions_posting.php @@ -1995,9 +1995,10 @@ function submit_post($mode, $subject, $username, $topic_type, &$poll, &$data, $u } } - $first_post_topic_info = ($post_mode == 'edit_first_post' && (($post_visibility == ITEM_DELETED && $data['topic_posts_softdeleted'] == 1) || - ($post_visibility == ITEM_UNAPPROVED && $data['topic_posts_unapproved'] == 1) || - ($post_visibility == ITEM_APPROVED && $data['topic_posts_approved'] == 1))); + $first_post_has_topic_info = ($post_mode == 'edit_first_post' && + (($post_visibility == ITEM_DELETED && $data['topic_posts_softdeleted'] == 1) || + ($post_visibility == ITEM_UNAPPROVED && $data['topic_posts_unapproved'] == 1) || + ($post_visibility == ITEM_APPROVED && $data['topic_posts_approved'] == 1))); // Fix the post's and topic's visibility and first/last post information, when the post is edited if (($post_mode != 'post' && $post_mode != 'reply') && $data['post_visibility'] != $post_visibility) { @@ -2010,7 +2011,7 @@ function submit_post($mode, $subject, $username, $topic_type, &$poll, &$data, $u $phpbb_content_visibility = $phpbb_container->get('content.visibility'); $phpbb_content_visibility->set_post_visibility($post_visibility, $data['post_id'], $data['topic_id'], $data['forum_id'], $user->data['user_id'], time(), '', $is_starter, $is_latest); } - else if ($post_mode == 'edit_last_post' || $post_mode == 'edit_topic' || $first_post_topic_info) + else if ($post_mode == 'edit_last_post' || $post_mode == 'edit_topic' || $first_post_has_topic_info) { if ($post_visibility == ITEM_APPROVED || $data['topic_visibility'] == $post_visibility) { -- cgit v1.2.1 From 580131b5c316925107d1c8bed586b1c6044f4c6e Mon Sep 17 00:00:00 2001 From: Bruno Ais Date: Mon, 22 Jul 2013 11:16:47 +0100 Subject: [ticket/11640] removed the unset sub-task of ticket PHPBB3-11635: find and fix all bypasses of generate_text_for_* PHPBB3-11640 --- phpBB/includes/functions_privmsgs.php | 1 - 1 file changed, 1 deletion(-) (limited to 'phpBB') diff --git a/phpBB/includes/functions_privmsgs.php b/phpBB/includes/functions_privmsgs.php index 001cf7bba0..15907feedd 100644 --- a/phpBB/includes/functions_privmsgs.php +++ b/phpBB/includes/functions_privmsgs.php @@ -2023,7 +2023,6 @@ function message_history($msg_id, $user_id, $message_row, $folder, $in_post_mode $parse_flags |= ($row['enable_smilies'] ? OPTION_FLAG_SMILIES : 0); $message = generate_text_for_display($message, $row['bbcode_uid'], $row['bbcode_bitfield'], $parse_flags , false); - unset($parse_flags); $subject = censor_text($subject); -- cgit v1.2.1 From 128af41a7ce983a1b54406e5c467e40b14af7afc Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Mon, 22 Jul 2013 12:47:32 +0200 Subject: [ticket/11725] Use new paths for phpbb_class_loader in file.php In the PR #1559, the paths were changed from "{$phpbb_root_path}includes/" to "{$phpbb_root_path}phpbb/" for the class loader. However, this was not changed in all files that use it. PHPBB3-11725 --- phpBB/download/file.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'phpBB') diff --git a/phpBB/download/file.php b/phpBB/download/file.php index cf7128b25b..5a091db7c7 100644 --- a/phpBB/download/file.php +++ b/phpBB/download/file.php @@ -41,7 +41,7 @@ if (isset($_GET['avatar'])) exit; } - require($phpbb_root_path . 'includes/class_loader.' . $phpEx); + require($phpbb_root_path . 'phpbb/class_loader.' . $phpEx); require($phpbb_root_path . 'includes/constants.' . $phpEx); require($phpbb_root_path . 'includes/functions.' . $phpEx); @@ -50,7 +50,7 @@ if (isset($_GET['avatar'])) require($phpbb_root_path . 'includes/utf/utf_tools.' . $phpEx); // Setup class loader first - $phpbb_class_loader = new phpbb_class_loader('phpbb_', "{$phpbb_root_path}includes/", $phpEx); + $phpbb_class_loader = new phpbb_class_loader('phpbb_', "{$phpbb_root_path}phpbb/", $phpEx); $phpbb_class_loader->register(); $phpbb_class_loader_ext = new phpbb_class_loader('phpbb_ext_', "{$phpbb_root_path}ext/", $phpEx); $phpbb_class_loader_ext->register(); -- cgit v1.2.1 From 0f708646241ed43c86793d8cbe0b5fea7397f0e6 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Mon, 22 Jul 2013 20:06:30 +0200 Subject: [ticket/11582] Move global declaration to beginning of block PHPBB3-11582 --- phpBB/includes/acp/acp_permission_roles.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'phpBB') diff --git a/phpBB/includes/acp/acp_permission_roles.php b/phpBB/includes/acp/acp_permission_roles.php index 5657cbe675..17e48d6576 100644 --- a/phpBB/includes/acp/acp_permission_roles.php +++ b/phpBB/includes/acp/acp_permission_roles.php @@ -25,7 +25,7 @@ class acp_permission_roles function main($id, $mode) { - global $db, $user, $auth, $template, $cache; + global $db, $user, $auth, $template, $cache, $phpbb_container; global $config, $phpbb_root_path, $phpbb_admin_path, $phpEx; include_once($phpbb_root_path . 'includes/functions_user.' . $phpEx); @@ -306,7 +306,6 @@ class acp_permission_roles trigger_error($user->lang['NO_ROLE_SELECTED'] . adm_back_link($this->u_action), E_USER_WARNING); } - global $phpbb_container; $phpbb_permissions = $phpbb_container->get('acl.permissions'); $template->assign_vars(array( -- cgit v1.2.1 From 974da6449c2f18f52086bd5ee6d24aafed046e37 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Mon, 22 Jul 2013 22:00:27 +0200 Subject: [ticket/11723] Correctly redirect user to agreement page and let him leave This patch consists of two changes. The first one will make sure that $agree is correctly reset to 0 and the user redirected back to the agreement page after changing the display language. Secondly, by reseting 'change_lang', the user will be able to agree to the terms on the agreement page again. The changed language will still be kept, as this is correctly saved in the 'lang' field that is passed to the ucp_register page. The variable $agree has also been changed to be boolean. It is not used as an integer anywere in the ucp_register file. PHPBB3-11723 --- phpBB/includes/ucp/ucp_register.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'phpBB') diff --git a/phpBB/includes/ucp/ucp_register.php b/phpBB/includes/ucp/ucp_register.php index 70fbfe46fb..7bc7ac8191 100644 --- a/phpBB/includes/ucp/ucp_register.php +++ b/phpBB/includes/ucp/ucp_register.php @@ -38,7 +38,7 @@ class ucp_register include($phpbb_root_path . 'includes/functions_profile_fields.' . $phpEx); $coppa = $request->is_set('coppa') ? (int) $request->variable('coppa', false) : false; - $agreed = (int) $request->variable('agreed', false); + $agreed = $request->variable('agreed', false); $submit = $request->is_set_post('submit'); $change_lang = request_var('change_lang', ''); $user_lang = request_var('lang', $user->lang_name); @@ -63,7 +63,7 @@ class ucp_register $submit = false; // Setting back agreed to let the user view the agreement in his/her language - $agreed = ($request->variable('change_lang', false)) ? 0 : $agreed; + $agreed = false; } $user->lang_name = $user_lang = $use_lang; @@ -89,7 +89,7 @@ class ucp_register $add_coppa = ($coppa !== false) ? '&coppa=' . $coppa : ''; $s_hidden_fields = array( - 'change_lang' => $change_lang, + 'change_lang' => '', ); // If we change the language, we want to pass on some more possible parameter. -- cgit v1.2.1 From aa77367267d381f1f87f1ed725e4f5f4a3135922 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Mon, 22 Jul 2013 22:54:55 +0200 Subject: [ticket/11728] Replace topic_approved with topic_visibility PHPBB3-11728 --- phpBB/phpbb/feed/topic.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB') diff --git a/phpBB/phpbb/feed/topic.php b/phpBB/phpbb/feed/topic.php index 696b0f5a52..bb1753d823 100644 --- a/phpBB/phpbb/feed/topic.php +++ b/phpBB/phpbb/feed/topic.php @@ -60,7 +60,7 @@ class phpbb_feed_topic extends phpbb_feed_post_base $this->forum_id = (int) $this->topic_data['forum_id']; // Make sure topic is either approved or user authed - if (!$this->topic_data['topic_approved'] && !$this->auth->acl_get('m_approve', $this->forum_id)) + if ($this->topic_data['topic_visibility'] != ITEM_APPROVED && !$this->auth->acl_get('m_approve', $this->forum_id)) { trigger_error('SORRY_AUTH_READ'); } -- cgit v1.2.1 From 80f81dd0d2d4aaef0b9c770d6071526aaca79e06 Mon Sep 17 00:00:00 2001 From: Andy Chase Date: Mon, 22 Jul 2013 15:04:30 -0700 Subject: [ticket/11731] Remove static calls to captcha garbage collector PHPBB3-11731 --- phpBB/includes/captcha/captcha_factory.php | 3 ++- phpBB/phpbb/session.php | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) (limited to 'phpBB') diff --git a/phpBB/includes/captcha/captcha_factory.php b/phpBB/includes/captcha/captcha_factory.php index 1ed8e119b5..fac45087e3 100644 --- a/phpBB/includes/captcha/captcha_factory.php +++ b/phpBB/includes/captcha/captcha_factory.php @@ -50,7 +50,8 @@ class phpbb_captcha_factory { include($phpbb_root_path . "includes/captcha/plugins/{$name}_plugin." . $phpEx); } - call_user_func(array($name, 'garbage_collect'), 0); + $captcha = self::get_instance($name); + $captcha->garbage_collect(0); } /** diff --git a/phpBB/phpbb/session.php b/phpBB/phpbb/session.php index e0585b1523..dc33786666 100644 --- a/phpBB/phpbb/session.php +++ b/phpBB/phpbb/session.php @@ -1022,7 +1022,8 @@ class phpbb_session { include($phpbb_root_path . "includes/captcha/captcha_factory." . $phpEx); } - phpbb_captcha_factory::garbage_collect($config['captcha_plugin']); + $captcha_factory = new phpbb_captcha_factory(); + $captcha_factory->garbage_collect($config['captcha_plugin']); $sql = 'DELETE FROM ' . LOGIN_ATTEMPT_TABLE . ' WHERE attempt_time < ' . (time() - (int) $config['ip_login_limit_time']); -- cgit v1.2.1 From 10ba1e73da8be9920708593592ab47493de4b101 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Tue, 23 Jul 2013 00:59:51 +0200 Subject: [ticket/11733] Fix "Illegal offset type" Warning caused by overall feed PHPBB3-11733 --- phpBB/phpbb/feed/overall.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB') diff --git a/phpBB/phpbb/feed/overall.php b/phpBB/phpbb/feed/overall.php index 869df7cde0..224d97ec03 100644 --- a/phpBB/phpbb/feed/overall.php +++ b/phpBB/phpbb/feed/overall.php @@ -72,7 +72,7 @@ class phpbb_feed_overall extends phpbb_feed_post_base ), ), 'WHERE' => $this->db->sql_in_set('p.topic_id', $topic_ids) . ' - AND ' . $this->content_visibility->get_visibility_sql('post', array(), 'p.') . ' + AND ' . $this->content_visibility->get_forums_visibility_sql('post', $forum_ids, 'p.') . ' AND p.post_time >= ' . $min_post_time . ' AND u.user_id = p.poster_id', 'ORDER_BY' => 'p.post_time DESC', -- cgit v1.2.1 From c36699811221b17c563a7525b790e0c6c3ab98e0 Mon Sep 17 00:00:00 2001 From: Bruno Ais Date: Tue, 23 Jul 2013 12:47:18 +0100 Subject: [ticket/11654] Moved some code to reduce line width. sub-task of ticket PHPBB3-11635: find and fix all bypasses of generate_text_for_* PHPBB3-11654 --- phpBB/includes/mcp/mcp_warn.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'phpBB') diff --git a/phpBB/includes/mcp/mcp_warn.php b/phpBB/includes/mcp/mcp_warn.php index 65cf641418..bb21d3d377 100644 --- a/phpBB/includes/mcp/mcp_warn.php +++ b/phpBB/includes/mcp/mcp_warn.php @@ -289,7 +289,8 @@ class mcp_warn // We want to make the message available here as a reminder // Parse the message and subject - $message = generate_text_for_display($user_row['post_text'], $user_row['bbcode_uid'], $user_row['bbcode_bitfield'], ($user_row['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES, true); + $parse_flags = OPTION_FLAG_SMILIES | ($row['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0); + $message = generate_text_for_display($user_row['post_text'], $user_row['bbcode_uid'], $user_row['bbcode_bitfield'], $parse_flags, true); // Generate the appropriate user information for the user we are looking at if (!function_exists('phpbb_get_user_avatar')) -- cgit v1.2.1 From 6b0b0f2a9fdd2e2169534756e9ceb4421125c7ea Mon Sep 17 00:00:00 2001 From: Nathaniel Guse Date: Sun, 14 Jul 2013 12:10:49 -0500 Subject: [ticket/11701] Loop variables are not passed correctly to events PHPBB3-11701 --- phpBB/phpbb/template/twig/lexer.php | 10 +++++----- phpBB/phpbb/template/twig/twig.php | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) (limited to 'phpBB') diff --git a/phpBB/phpbb/template/twig/lexer.php b/phpBB/phpbb/template/twig/lexer.php index 4f88147542..606ca347ae 100644 --- a/phpBB/phpbb/template/twig/lexer.php +++ b/phpBB/phpbb/template/twig/lexer.php @@ -191,20 +191,20 @@ class phpbb_template_twig_lexer extends Twig_Lexer // Recursive...fix any child nodes $body = $parent_class->fix_begin_tokens($body, $parent_nodes); - // Rename loopname vars (to prevent collisions, loop children are named (loop name)_loop_element) - $body = str_replace($name . '.', $name . '_loop_element.', $body); + // Rename loopname vars + $body = str_replace($name . '.', $name . '.', $body); // Need the parent variable name array_pop($parent_nodes); - $parent = (!empty($parent_nodes)) ? end($parent_nodes) . '_loop_element.' : ''; + $parent = (!empty($parent_nodes)) ? end($parent_nodes) . '.' : ''; if ($subset !== '') { $subset = '|subset(' . $subset . ')'; } - // Turn into a Twig for loop, using (loop name)_loop_element for each child - return "{% for {$name}_loop_element in {$parent}{$name}{$subset} %}{$body}{% endfor %}"; + // Turn into a Twig for loop + return "{% for {$name} in loops.{$parent}{$name}{$subset} %}{$body}{% endfor %}"; }; // Replace correctly, only needs to be done once diff --git a/phpBB/phpbb/template/twig/twig.php b/phpBB/phpbb/template/twig/twig.php index 92a37d1634..6cff1bb8e4 100644 --- a/phpBB/phpbb/template/twig/twig.php +++ b/phpBB/phpbb/template/twig/twig.php @@ -429,15 +429,15 @@ class phpbb_template_twig implements phpbb_template $vars = array_merge( $context_vars['.'][0], // To get normal vars - $context_vars, // To get loops array( 'definition' => new phpbb_template_twig_definition(), 'user' => $this->user, + 'loops' => $context_vars, // To get loops ) ); // cleanup - unset($vars['.']); + unset($vars['loops']['.']); return $vars; } -- cgit v1.2.1 From 0d31420ae099b9284c6240bfbda0f03f1be155c1 Mon Sep 17 00:00:00 2001 From: Nathaniel Guse Date: Sun, 14 Jul 2013 12:18:44 -0500 Subject: [ticket/11701] Remove useless str_replace PHPBB3-11701 --- phpBB/phpbb/template/twig/lexer.php | 3 --- 1 file changed, 3 deletions(-) (limited to 'phpBB') diff --git a/phpBB/phpbb/template/twig/lexer.php b/phpBB/phpbb/template/twig/lexer.php index 606ca347ae..3c072adff9 100644 --- a/phpBB/phpbb/template/twig/lexer.php +++ b/phpBB/phpbb/template/twig/lexer.php @@ -191,9 +191,6 @@ class phpbb_template_twig_lexer extends Twig_Lexer // Recursive...fix any child nodes $body = $parent_class->fix_begin_tokens($body, $parent_nodes); - // Rename loopname vars - $body = str_replace($name . '.', $name . '.', $body); - // Need the parent variable name array_pop($parent_nodes); $parent = (!empty($parent_nodes)) ? end($parent_nodes) . '.' : ''; -- cgit v1.2.1 From 1d2d3032d3f0f614b0df9851c373618e205580f8 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Tue, 23 Jul 2013 16:26:01 +0200 Subject: [ticket/11734] Readd accidently removed language strings of forum permissions PHPBB3-11734 --- phpBB/language/en/acp/permissions_phpbb.php | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'phpBB') diff --git a/phpBB/language/en/acp/permissions_phpbb.php b/phpBB/language/en/acp/permissions_phpbb.php index d0128db34a..5ea151f6ea 100644 --- a/phpBB/language/en/acp/permissions_phpbb.php +++ b/phpBB/language/en/acp/permissions_phpbb.php @@ -102,6 +102,17 @@ $lang = array_merge($lang, array( // Forum Permissions $lang = array_merge($lang, array( + 'ACL_F_LIST' => 'Can see forum', + 'ACL_F_READ' => 'Can read forum', + 'ACL_F_SEARCH' => 'Can search the forum', + 'ACL_F_SUBSCRIBE' => 'Can subscribe forum', + 'ACL_F_PRINT' => 'Can print topics', + 'ACL_F_EMAIL' => 'Can email topics', + 'ACL_F_BUMP' => 'Can bump topics', + 'ACL_F_USER_LOCK' => 'Can lock own topics', + 'ACL_F_DOWNLOAD' => 'Can download files', + 'ACL_F_REPORT' => 'Can report posts', + 'ACL_F_POST' => 'Can start new topics', 'ACL_F_STICKY' => 'Can post stickies', 'ACL_F_ANNOUNCE' => 'Can post announcements', -- cgit v1.2.1 From c0b9db1c626c88780b2ee5f5a237561a125c54aa Mon Sep 17 00:00:00 2001 From: Nathaniel Guse Date: Sun, 14 Jul 2013 14:10:11 -0500 Subject: [ticket/11701] Fix loops var check PHPBB3-11701 --- phpBB/phpbb/template/twig/lexer.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'phpBB') diff --git a/phpBB/phpbb/template/twig/lexer.php b/phpBB/phpbb/template/twig/lexer.php index 3c072adff9..4f127bd7e6 100644 --- a/phpBB/phpbb/template/twig/lexer.php +++ b/phpBB/phpbb/template/twig/lexer.php @@ -200,8 +200,9 @@ class phpbb_template_twig_lexer extends Twig_Lexer $subset = '|subset(' . $subset . ')'; } + $parent = ($parent) ?: 'loops.'; // Turn into a Twig for loop - return "{% for {$name} in loops.{$parent}{$name}{$subset} %}{$body}{% endfor %}"; + return "{% for {$name} in {$parent}{$name}{$subset} %}{$body}{% endfor %}"; }; // Replace correctly, only needs to be done once @@ -224,7 +225,10 @@ class phpbb_template_twig_lexer extends Twig_Lexer // Replace $TEST with definition.TEST $inner = preg_replace('#\s\$([a-zA-Z_0-9]+)#', ' definition.$1', $inner); - // Replace .test with test|length + // Replace .foo with loops.foo|length + $inner = preg_replace('#\s\.([a-zA-Z_0-9]+)#', ' loops.$1|length', $inner); + + // Replace .foo.bar with foo.bar|length $inner = preg_replace('#\s\.([a-zA-Z_0-9\.]+)#', ' $1|length', $inner); return ""; -- cgit v1.2.1 From 2d764ef7d54104be2925aa015193da20c4383c5d Mon Sep 17 00:00:00 2001 From: Nathaniel Guse Date: Fri, 19 Jul 2013 11:31:52 -0500 Subject: [ticket/11701] Fix regex for appending |length PHPBB3-11701 --- phpBB/phpbb/template/twig/lexer.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'phpBB') diff --git a/phpBB/phpbb/template/twig/lexer.php b/phpBB/phpbb/template/twig/lexer.php index 4f127bd7e6..95b3043403 100644 --- a/phpBB/phpbb/template/twig/lexer.php +++ b/phpBB/phpbb/template/twig/lexer.php @@ -226,10 +226,10 @@ class phpbb_template_twig_lexer extends Twig_Lexer $inner = preg_replace('#\s\$([a-zA-Z_0-9]+)#', ' definition.$1', $inner); // Replace .foo with loops.foo|length - $inner = preg_replace('#\s\.([a-zA-Z_0-9]+)#', ' loops.$1|length', $inner); + $inner = preg_replace('#\s\.([a-zA-Z_0-9]+) #', ' loops.$1|length ', $inner); // Replace .foo.bar with foo.bar|length - $inner = preg_replace('#\s\.([a-zA-Z_0-9\.]+)#', ' $1|length', $inner); + $inner = preg_replace('#\s\.([a-zA-Z_0-9\.]+) #', ' $1|length ', $inner); return ""; }; -- cgit v1.2.1 From ea250a5ef5dd3e790b22336e5a9e74c77da35569 Mon Sep 17 00:00:00 2001 From: Nathaniel Guse Date: Fri, 19 Jul 2013 12:43:24 -0500 Subject: [ticket/11701] Refix regex for appending |length PHPBB3-11701 --- phpBB/phpbb/template/twig/lexer.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'phpBB') diff --git a/phpBB/phpbb/template/twig/lexer.php b/phpBB/phpbb/template/twig/lexer.php index 95b3043403..3534311b7a 100644 --- a/phpBB/phpbb/template/twig/lexer.php +++ b/phpBB/phpbb/template/twig/lexer.php @@ -226,10 +226,10 @@ class phpbb_template_twig_lexer extends Twig_Lexer $inner = preg_replace('#\s\$([a-zA-Z_0-9]+)#', ' definition.$1', $inner); // Replace .foo with loops.foo|length - $inner = preg_replace('#\s\.([a-zA-Z_0-9]+) #', ' loops.$1|length ', $inner); + $inner = preg_replace('#\s\.([a-zA-Z_0-9]+)([^a-zA-Z_0-9\.])#', ' loops.$1|length$2', $inner); // Replace .foo.bar with foo.bar|length - $inner = preg_replace('#\s\.([a-zA-Z_0-9\.]+) #', ' $1|length ', $inner); + $inner = preg_replace('#\s\.([a-zA-Z_0-9\.]+)([^a-zA-Z_0-9\.])#', ' $1|length$2', $inner); return ""; }; -- cgit v1.2.1 From bf04bfcced7934704e7f2682ee608f490cb3fc76 Mon Sep 17 00:00:00 2001 From: Nathan Guse Date: Tue, 23 Jul 2013 11:16:23 -0500 Subject: [ticket/11667] Use @inheritdoc PHPBB3-11667 --- phpBB/phpbb/template/twig/node/includecss.php | 9 ++------- phpBB/phpbb/template/twig/node/includejs.php | 9 ++------- 2 files changed, 4 insertions(+), 14 deletions(-) (limited to 'phpBB') diff --git a/phpBB/phpbb/template/twig/node/includecss.php b/phpBB/phpbb/template/twig/node/includecss.php index 450edb3e1e..a9d9b46d69 100644 --- a/phpBB/phpbb/template/twig/node/includecss.php +++ b/phpBB/phpbb/template/twig/node/includecss.php @@ -10,9 +10,7 @@ class phpbb_template_twig_node_includecss extends phpbb_template_twig_node_includeasset { /** - * Get the definition name - * - * @return string (e.g. 'SCRIPTS') + * {@inheritdoc} */ public function get_definition_name() { @@ -20,10 +18,7 @@ class phpbb_template_twig_node_includecss extends phpbb_template_twig_node_inclu } /** - * Append the output code for the asset - * - * @param Twig_Compiler A Twig_Compiler instance - * @return null + * {@inheritdoc} */ public function append_asset(Twig_Compiler $compiler) { diff --git a/phpBB/phpbb/template/twig/node/includejs.php b/phpBB/phpbb/template/twig/node/includejs.php index 50ab448e0f..2b4b55fb0a 100644 --- a/phpBB/phpbb/template/twig/node/includejs.php +++ b/phpBB/phpbb/template/twig/node/includejs.php @@ -10,9 +10,7 @@ class phpbb_template_twig_node_includejs extends phpbb_template_twig_node_includeasset { /** - * Get the definition name - * - * @return string (e.g. 'SCRIPTS') + * {@inheritdoc} */ public function get_definition_name() { @@ -20,10 +18,7 @@ class phpbb_template_twig_node_includejs extends phpbb_template_twig_node_includ } /** - * Append the output code for the asset - * - * @param Twig_Compiler A Twig_Compiler instance - * @return null + * {@inheritdoc} */ protected function append_asset(Twig_Compiler $compiler) { -- cgit v1.2.1 From 1372b4f20801d6079798832f15caf38949fe9333 Mon Sep 17 00:00:00 2001 From: Bruno Ais Date: Wed, 24 Jul 2013 10:33:06 +0100 Subject: [ticket/11639] Removed a non-needed unset sub-task of ticket PHPBB3-11635: find and fix all bypasses of generate_text_for_* PHPBB3-11639 --- phpBB/includes/functions_posting.php | 2 -- 1 file changed, 2 deletions(-) (limited to 'phpBB') diff --git a/phpBB/includes/functions_posting.php b/phpBB/includes/functions_posting.php index 49a1797321..acf0cc874b 100644 --- a/phpBB/includes/functions_posting.php +++ b/phpBB/includes/functions_posting.php @@ -1110,8 +1110,6 @@ function topic_review($topic_id, $forum_id, $mode = 'topic_review', $cur_post_id // Do not censor text because it has already been censored before $message = generate_text_for_display($message, $row['bbcode_uid'], $row['bbcode_bitfield'], $parse_flags, false); - unset($parse_flags); - if (!empty($attachments[$row['post_id']])) { $update_count = array(); -- cgit v1.2.1 From 13fa346e8f5b496f5e51ea20e9420a48228a5072 Mon Sep 17 00:00:00 2001 From: Bruno Ais Date: Wed, 24 Jul 2013 11:38:04 +0100 Subject: [ticket/11656] Made the check for the bitfield just like other PR's sub-task of ticket PHPBB3-11635: find and fix all bypasses of generate_text_for_* PHPBB3-11656 --- phpBB/memberlist.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'phpBB') diff --git a/phpBB/memberlist.php b/phpBB/memberlist.php index f8ee82084c..018526d034 100644 --- a/phpBB/memberlist.php +++ b/phpBB/memberlist.php @@ -561,7 +561,8 @@ switch ($mode) if ($member['user_sig']) { - $member['user_sig'] = generate_text_for_display($member['user_sig'], $member['user_sig_bbcode_uid'], $member['user_sig_bbcode_bitfield'], OPTION_FLAG_BBCODE | OPTION_FLAG_SMILIES, true); + $parse_flags = ($member['user_sig_bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES; + $member['user_sig'] = generate_text_for_display($member['user_sig'], $member['user_sig_bbcode_uid'], $member['user_sig_bbcode_bitfield'], $parse_flags, true); } $poster_avatar = phpbb_get_user_avatar($member); -- cgit v1.2.1 From 4ed322b5b8642ec8d0a6faf23d9ea751e81dbf69 Mon Sep 17 00:00:00 2001 From: Bruno Ais Date: Wed, 24 Jul 2013 11:59:28 +0100 Subject: [ticket/11638] Updated: bitwise $parse_flags use optionset() sub-task of ticket PHPBB3-11635: find and fix all bypasses of generate_text_for_* PHPBB3-11638 --- phpBB/viewtopic.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'phpBB') diff --git a/phpBB/viewtopic.php b/phpBB/viewtopic.php index de76d1186d..303b9bb6da 100644 --- a/phpBB/viewtopic.php +++ b/phpBB/viewtopic.php @@ -1381,8 +1381,9 @@ for ($i = 0, $end = sizeof($post_list); $i < $end; ++$i) // End signature parsing, only if needed if ($user_cache[$poster_id]['sig'] && $row['enable_sig'] && empty($user_cache[$poster_id]['sig_parsed'])) { - $include_bbcode_parse = $user_cache[$poster_id]['sig_bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0; - $user_cache[$poster_id]['sig'] = generate_text_for_display($user_cache[$poster_id]['sig'], $user_cache[$poster_id]['sig_bbcode_uid'], $user_cache[$poster_id]['sig_bbcode_bitfield'], $include_bbcode_parse | OPTION_FLAG_SMILIES, true); + $parse_flags = phpbb_optionset(OPTION_FLAG_SMILIES, true, 0); + $parse_flags = phpbb_optionset(OPTION_FLAG_BBCODE, $user_cache[$poster_id]['sig_bbcode_bitfield'], $parse_flag); + $user_cache[$poster_id]['sig'] = generate_text_for_display($user_cache[$poster_id]['sig'], $user_cache[$poster_id]['sig_bbcode_uid'], $parse_flags, true); } // Parse the message and subject -- cgit v1.2.1 From 0dcf24acc10e7ad52bf47c39faf14b118fd1566d Mon Sep 17 00:00:00 2001 From: Oliver Schramm Date: Wed, 24 Jul 2013 12:41:40 +0200 Subject: [ticket/8228] Fix whitespaces before code in Firefox Based on the "[code] enhancements" modification by TerraFrost PHPBB3-8228 --- phpBB/styles/prosilver/template/bbcode.html | 4 ++-- phpBB/styles/prosilver/template/forum_fn.js | 5 +++-- phpBB/styles/prosilver/theme/colours.css | 2 +- phpBB/styles/prosilver/theme/content.css | 2 +- phpBB/styles/subsilver2/template/bbcode.html | 4 ++-- 5 files changed, 9 insertions(+), 8 deletions(-) (limited to 'phpBB') diff --git a/phpBB/styles/prosilver/template/bbcode.html b/phpBB/styles/prosilver/template/bbcode.html index c0c0a2da46..460d102c28 100644 --- a/phpBB/styles/prosilver/template/bbcode.html +++ b/phpBB/styles/prosilver/template/bbcode.html @@ -12,8 +12,8 @@
-
{L_CODE}{L_COLON} {L_SELECT_ALL_CODE}
-
+
{L_CODE}{L_COLON} {L_SELECT_ALL_CODE}

+
diff --git a/phpBB/styles/prosilver/template/forum_fn.js b/phpBB/styles/prosilver/template/forum_fn.js index eccb12e827..42a68a2ef5 100644 --- a/phpBB/styles/prosilver/template/forum_fn.js +++ b/phpBB/styles/prosilver/template/forum_fn.js @@ -187,7 +187,7 @@ function displayBlocks(c, e, t) { function selectCode(a) { // Get ID of code block - var e = a.parentNode.parentNode.getElementsByTagName('CODE')[0]; + var e = a.parentNode.parentNode.getElementsByTagName('PRE')[0]; var s, r; // Not IE and IE9+ @@ -205,7 +205,8 @@ function selectCode(a) { } r = document.createRange(); - r.selectNodeContents(e); + r.setStart(e.firstChild, 0); + r.setEnd(e.lastChild, e.lastChild.textContent.length); s.removeAllRanges(); s.addRange(r); } diff --git a/phpBB/styles/prosilver/theme/colours.css b/phpBB/styles/prosilver/theme/colours.css index 7d0462be1b..801d607d9c 100644 --- a/phpBB/styles/prosilver/theme/colours.css +++ b/phpBB/styles/prosilver/theme/colours.css @@ -479,7 +479,7 @@ dl.codebox dt { border-bottom-color: #CCCCCC; } -dl.codebox code { +dl.codebox pre { color: #2E8B57; } diff --git a/phpBB/styles/prosilver/theme/content.css b/phpBB/styles/prosilver/theme/content.css index 4b8c972697..0cab12910b 100644 --- a/phpBB/styles/prosilver/theme/content.css +++ b/phpBB/styles/prosilver/theme/content.css @@ -491,7 +491,7 @@ blockquote dl.codebox { margin-left: 0; } -dl.codebox code { +dl.codebox pre { /* Also see tweaks.css */ overflow: auto; display: block; diff --git a/phpBB/styles/subsilver2/template/bbcode.html b/phpBB/styles/subsilver2/template/bbcode.html index efcf5e1acb..5558716cad 100644 --- a/phpBB/styles/subsilver2/template/bbcode.html +++ b/phpBB/styles/subsilver2/template/bbcode.html @@ -21,11 +21,11 @@ -
{L_CODE}{L_COLON}
+
{L_CODE}{L_COLON}
 
 
 
-
+ -- cgit v1.2.1 From 029015e1542e7719d4a35d350093f4dfb3a7313e Mon Sep 17 00:00:00 2001 From: Bruno Ais Date: Wed, 24 Jul 2013 12:31:14 +0100 Subject: [ticket/11638] Reverted to use the $parse tags way as the other ones sub-task of ticket PHPBB3-11635: find and fix all bypasses of generate_text_for_* PHPBB3-11638 --- phpBB/viewtopic.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'phpBB') diff --git a/phpBB/viewtopic.php b/phpBB/viewtopic.php index 303b9bb6da..a0e7eb6a94 100644 --- a/phpBB/viewtopic.php +++ b/phpBB/viewtopic.php @@ -1381,9 +1381,8 @@ for ($i = 0, $end = sizeof($post_list); $i < $end; ++$i) // End signature parsing, only if needed if ($user_cache[$poster_id]['sig'] && $row['enable_sig'] && empty($user_cache[$poster_id]['sig_parsed'])) { - $parse_flags = phpbb_optionset(OPTION_FLAG_SMILIES, true, 0); - $parse_flags = phpbb_optionset(OPTION_FLAG_BBCODE, $user_cache[$poster_id]['sig_bbcode_bitfield'], $parse_flag); - $user_cache[$poster_id]['sig'] = generate_text_for_display($user_cache[$poster_id]['sig'], $user_cache[$poster_id]['sig_bbcode_uid'], $parse_flags, true); + $parse_flags = ($user_cache[$poster_id]['sig_bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES; + $user_cache[$poster_id]['sig'] = generate_text_for_display($user_cache[$poster_id]['sig'], $user_cache[$poster_id]['sig_bbcode_uid'], $user_cache[$poster_id]['sig_bbcode_bitfield'], $parse_flags, true); } // Parse the message and subject -- cgit v1.2.1 From 6c68348a71891219b039ae98a5a0af2fd52d3956 Mon Sep 17 00:00:00 2001 From: Bruno Ais Date: Wed, 24 Jul 2013 12:31:38 +0100 Subject: [ticket/11638] Use the $parse_flags like the other commits sub-task of ticket PHPBB3-11635: find and fix all bypasses of generate_text_for_* PHPBB3-11638 --- phpBB/viewtopic.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'phpBB') diff --git a/phpBB/viewtopic.php b/phpBB/viewtopic.php index a0e7eb6a94..151176fc6f 100644 --- a/phpBB/viewtopic.php +++ b/phpBB/viewtopic.php @@ -1386,7 +1386,8 @@ for ($i = 0, $end = sizeof($post_list); $i < $end; ++$i) } // Parse the message and subject - $message = generate_text_for_display($row['post_text'], $row['bbcode_uid'], $row['bbcode_bitfield'], ($row['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES, true); + $parse_flags = ($row['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES; + $message = generate_text_for_display($row['post_text'], $row['bbcode_uid'], $row['bbcode_bitfield'], $parse_flags, true); if (!empty($attachments[$row['post_id']])) { -- cgit v1.2.1 From 4cdccbd42b2f6ee54c6edb7f96ec3a0cac2ae8ea Mon Sep 17 00:00:00 2001 From: Bruno Ais Date: Wed, 24 Jul 2013 12:45:23 +0100 Subject: [ticket/11638] Removed the unneeded reset. sub-task of ticket PHPBB3-11635: find and fix all bypasses of generate_text_for_* PHPBB3-11638 --- phpBB/viewtopic.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'phpBB') diff --git a/phpBB/viewtopic.php b/phpBB/viewtopic.php index 151176fc6f..64006fbe61 100644 --- a/phpBB/viewtopic.php +++ b/phpBB/viewtopic.php @@ -830,7 +830,7 @@ if (!empty($topic_data['poll_start'])) $parse_bbcode_flags = OPTION_FLAG_SMILIES; - if(empty($poll_info[0]['bbcode_bitfield'])) + if (empty($poll_info[0]['bbcode_bitfield'])) { $parse_bbcode_flags |= OPTION_FLAG_BBCODE; } @@ -841,8 +841,6 @@ if (!empty($topic_data['poll_start'])) } $topic_data['poll_title'] = generate_text_for_display($topic_data['poll_title'], $poll_info[0]['bbcode_uid'], $poll_info[0]['bbcode_bitfield'], $parse_bbcode_flags, true); - - unset($parse_bbcode_flags); foreach ($poll_info as $poll_option) { -- cgit v1.2.1 From 27126e065dd6a8de44c9da718b4b5b9895544bb1 Mon Sep 17 00:00:00 2001 From: Oliver Schramm Date: Wed, 24 Jul 2013 17:22:10 +0200 Subject: [ticket/11741] Fix empty brackets and remove bullet PHPBB3-11741 --- phpBB/styles/subsilver2/template/overall_header.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'phpBB') diff --git a/phpBB/styles/subsilver2/template/overall_header.html b/phpBB/styles/subsilver2/template/overall_header.html index bc2307154b..b0d7ce6fab 100644 --- a/phpBB/styles/subsilver2/template/overall_header.html +++ b/phpBB/styles/subsilver2/template/overall_header.html @@ -154,8 +154,8 @@ function marklist(id, name, state) + + @@ -23,7 +25,33 @@ var text_name = 'signature'; // ]]> - + +
- - [ {NOTIFICATIONS_COUNT} ] • + + [ {NOTIFICATIONS_COUNT}
{L_NOTIFICATIONS} -- cgit v1.2.1 From 5af63a7860678ac55f84201cf7dbbdf82dcdc8e8 Mon Sep 17 00:00:00 2001 From: Oliver Schramm Date: Wed, 24 Jul 2013 18:15:03 +0200 Subject: [ticket/10917] Fixed notice that files are out of date when updating to an unreleased version PHPBB3-10917 --- phpBB/install/install_update.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB') diff --git a/phpBB/install/install_update.php b/phpBB/install/install_update.php index c18a0fb4ec..5393040061 100644 --- a/phpBB/install/install_update.php +++ b/phpBB/install/install_update.php @@ -204,7 +204,7 @@ class install_update extends module } // Check if the update files stored are for the latest version... - if ($this->latest_version != $this->update_info['version']['to']) + if (version_compare(strtolower($this->latest_version), strtolower($this->update_info['version']['to']), '>')) { $this->unequal_version = true; -- cgit v1.2.1 From 98b385bc1c14a3155dd429f8d9118f4d7eb95556 Mon Sep 17 00:00:00 2001 From: Nathaniel Guse Date: Wed, 24 Jul 2013 11:59:21 -0500 Subject: [ticket/11628] Remove style resource locator No longer used since Twig was implemented. PHPBB3-11628 --- phpBB/config/services.yml | 4 - phpBB/includes/bbcode.php | 3 +- phpBB/install/index.php | 3 +- phpBB/phpbb/style/resource_locator.php | 348 --------------------------------- phpBB/phpbb/style/style.php | 45 +---- phpBB/phpbb/template/locator.php | 163 --------------- 6 files changed, 3 insertions(+), 563 deletions(-) delete mode 100644 phpBB/phpbb/style/resource_locator.php delete mode 100644 phpBB/phpbb/template/locator.php (limited to 'phpBB') diff --git a/phpBB/config/services.yml b/phpBB/config/services.yml index 8abc413a5a..4902f4b6f6 100644 --- a/phpBB/config/services.yml +++ b/phpBB/config/services.yml @@ -258,13 +258,9 @@ services: - %core.php_ext% - @config - @user - - @style.resource_locator - @style.path_provider_ext - @template - style.resource_locator: - class: phpbb_style_resource_locator - style.path_provider_ext: class: phpbb_style_extension_path_provider arguments: diff --git a/phpBB/includes/bbcode.php b/phpBB/includes/bbcode.php index fd00728510..4ce6f17d90 100644 --- a/phpBB/includes/bbcode.php +++ b/phpBB/includes/bbcode.php @@ -132,10 +132,9 @@ class bbcode { $this->template_bitfield = new bitfield($user->style['bbcode_bitfield']); - $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(), $phpbb_root_path); $template = new phpbb_template_twig($phpbb_root_path, $phpEx, $config, $user, 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 = new phpbb_style($phpbb_root_path, $phpEx, $config, $user, $style_path_provider, $template); $style->set_style(); $template->set_filenames(array('bbcode.html' => 'bbcode.html')); $this->template_filename = $template->get_source_file_for_handle('bbcode.html'); diff --git a/phpBB/install/index.php b/phpBB/install/index.php index 45e5777e36..f924b05547 100644 --- a/phpBB/install/index.php +++ b/phpBB/install/index.php @@ -212,10 +212,9 @@ $config = new phpbb_config(array( 'load_tplcompile' => '1' )); -$phpbb_style_resource_locator = new phpbb_style_resource_locator(); $phpbb_style_path_provider = new phpbb_style_path_provider(); $template = new phpbb_template_twig($phpbb_root_path, $phpEx, $config, $user, 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 = new phpbb_style($phpbb_root_path, $phpEx, $config, $user, $phpbb_style_path_provider, $template); $phpbb_style->set_ext_dir_prefix('adm/'); $phpbb_style->set_custom_style('admin', $phpbb_admin_path . 'style', array(), ''); $template->assign_var('T_ASSETS_PATH', '../assets'); diff --git a/phpBB/phpbb/style/resource_locator.php b/phpBB/phpbb/style/resource_locator.php deleted file mode 100644 index 4cf767c062..0000000000 --- a/phpBB/phpbb/style/resource_locator.php +++ /dev/null @@ -1,348 +0,0 @@ -set_default_template_path(); - } - - /** - * Sets the list of style paths - * - * These paths will be searched for style files in the provided order. - * Paths may be outside of phpBB, but templates loaded from these paths - * will still be cached. - * - * @param array $style_paths An array of paths to style directories - * @return null - */ - public function set_paths($style_paths) - { - $this->roots = array(); - $this->files = array(); - $this->filenames = array(); - - foreach ($style_paths as $key => $paths) - { - foreach ($paths as $path) - { - // Make sure $path has no ending slash - if (substr($path, -1) === '/') - { - $path = substr($path, 0, -1); - } - $this->roots[$key][] = $path; - } - } - } - - /** - * Sets the location of templates directory within style directories. - * - * The location must be a relative path, with a trailing slash. - * Typically it is one directory level deep, e.g. "template/". - * - * @param string $template_path Relative path to templates directory within style directories - * @return null - */ - public function set_template_path($template_path) - { - $this->template_path = $template_path; - } - - /** - * Sets the location of templates directory within style directories - * to the default, which is "template/". - * - * @return null - */ - public function set_default_template_path() - { - $this->template_path = 'template/'; - } - - /** - * {@inheritDoc} - */ - public function set_filenames(array $filename_array) - { - foreach ($filename_array as $handle => $filename) - { - if (empty($filename)) - { - trigger_error("style resource locator: set_filenames: Empty filename specified for $handle", E_USER_ERROR); - } - - $this->filename[$handle] = $filename; - - foreach ($this->roots as $root_key => $root_paths) - { - foreach ($root_paths as $root_index => $root) - { - $this->files[$root_key][$root_index][$handle] = $root . '/' . $this->template_path . $filename; - } - } - } - } - - /** - * {@inheritDoc} - */ - public function get_filename_for_handle($handle) - { - if (!isset($this->filename[$handle])) - { - trigger_error("style resource locator: get_filename_for_handle: No file specified for handle $handle", E_USER_ERROR); - } - return $this->filename[$handle]; - } - - /** - * {@inheritDoc} - */ - public function get_virtual_source_file_for_handle($handle) - { - // If we don't have a file assigned to this handle, die. - if (!isset($this->files['style'][0][$handle])) - { - trigger_error("style resource locator: No file specified for handle $handle", E_USER_ERROR); - } - - $source_file = $this->files['style'][0][$handle]; - return $source_file; - } - - /** - * {@inheritDoc} - */ - public function get_source_file_for_handle($handle, $find_all = false) - { - // If we don't have a file assigned to this handle, die. - if (!isset($this->files['style'][0][$handle])) - { - trigger_error("style resource locator: No file specified for handle $handle", E_USER_ERROR); - } - - // locate a source file that exists - $source_file = $this->files['style'][0][$handle]; - $tried = $source_file; - $found = false; - $found_all = array(); - foreach ($this->roots as $root_key => $root_paths) - { - foreach ($root_paths as $root_index => $root) - { - $source_file = $this->files[$root_key][$root_index][$handle]; - $tried .= ', ' . $source_file; - if (file_exists($source_file)) - { - $found = true; - break; - } - } - if ($found) - { - if ($find_all) - { - $found_all[] = $source_file; - $found = false; - } - else - { - break; - } - } - } - - // search failed - if (!$found && !$find_all) - { - trigger_error("style resource locator: File for handle $handle does not exist. Could not find: $tried", E_USER_ERROR); - } - - return ($find_all) ? $found_all : $source_file; - } - - /** - * {@inheritDoc} - */ - public function get_first_file_location($files, $return_default = false, $return_full_path = true) - { - // set default value - $default_result = false; - - // check all available paths - foreach ($this->roots as $root_paths) - { - foreach ($root_paths as $path) - { - // check all files - foreach ($files as $filename) - { - $source_file = $path . '/' . $filename; - if (file_exists($source_file)) - { - return ($return_full_path) ? $source_file : $filename; - } - - // assign first file as result if $return_default is true - if ($return_default && $default_result === false) - { - $default_result = $source_file; - } - } - } - } - - // search failed - return $default_result; - } - - /** - * Obtains filesystem path for a template file. - * - * The simplest use is specifying a single template file as a string - * in the first argument. This template file should be a basename - * of a template file in the selected style, or its parent styles - * if template inheritance is being utilized. - * - * Note: "selected style" is whatever style the style resource locator - * is configured for. - * - * The return value then will be a path, relative to the current - * directory or absolute, to the template file in the selected style - * or its closest parent. - * - * If the selected style does not have the template file being searched, - * (and if inheritance is involved, none of the parents have it either), - * false will be returned. - * - * Specifying true for $return_default will cause the function to - * return the first path which was checked for existence in the event - * that the template file was not found, instead of false. - * This is the path in the selected style itself, not any of its - * parents. - * - * $files can be given an array of templates instead of a single - * template. When given an array, the function will try to resolve - * each template in the array to a path, and will return the first - * path that exists, or false if none exist. - * - * If $files is an array and template inheritance is involved, first - * each of the files will be checked in the selected style, then each - * of the files will be checked in the immediate parent, and so on. - * - * If $return_full_path is false, then instead of returning a usable - * path (when the template is found) only the template's basename - * will be returned. This can be used to check which of the templates - * specified in $files exists. Naturally more than one template must - * be given in $files. - * - * This function works identically to get_first_file_location except - * it operates on a list of templates, not files. Practically speaking, - * the templates given in the first argument first are prepended with - * the template path (property in this class), then given to - * get_first_file_location for the rest of the processing. - * - * Templates given to this function can be relative paths for templates - * located in subdirectories of the template directories. The paths - * should be relative to the templates directory (template/ by default). - * - * @param string or array $files List of templates to locate. If there is only - * one template, $files can be a string to make code easier to read. - * @param bool $return_default Determines what to return if template does not - * exist. If true, function will return location where template is - * supposed to be. If false, function will return false. - * @param bool $return_full_path If true, function will return full path - * to template. If false, function will return template file name. - * This parameter can be used to check which one of set of template - * files is available. - * @return string or boolean Source template path if template exists or $return_default is - * true. False if template does not exist and $return_default is false - */ - public function get_first_template_location($templates, $return_default = false, $return_full_path = true) - { - // add template path prefix - $files = array(); - if (is_string($templates)) - { - $files[] = $this->template_path . $templates; - } - else - { - foreach ($templates as $template) - { - $files[] = $this->template_path . $template; - } - } - - return $this->get_first_file_location($files, $return_default, $return_full_path); - } -} diff --git a/phpBB/phpbb/style/style.php b/phpBB/phpbb/style/style.php index 034f518091..9756fb74ac 100644 --- a/phpBB/phpbb/style/style.php +++ b/phpBB/phpbb/style/style.php @@ -52,12 +52,6 @@ class phpbb_style */ private $user; - /** - * Style resource locator - * @var phpbb_style_resource_locator - */ - private $locator; - /** * Style path provider * @var phpbb_style_path_provider @@ -69,17 +63,15 @@ class phpbb_style * * @param string $phpbb_root_path phpBB root path * @param user $user current user - * @param phpbb_style_resource_locator $locator style resource locator * @param phpbb_style_path_provider $provider style path provider * @param phpbb_template $template template */ - public function __construct($phpbb_root_path, $php_ext, $config, $user, phpbb_style_resource_locator $locator, phpbb_style_path_provider_interface $provider, phpbb_template $template) + public function __construct($phpbb_root_path, $php_ext, $config, $user, phpbb_style_path_provider_interface $provider, phpbb_template $template) { $this->phpbb_root_path = $phpbb_root_path; $this->php_ext = $php_ext; $this->config = $config; $this->user = $user; - $this->locator = $locator; $this->provider = $provider; $this->template = $template; } @@ -130,7 +122,6 @@ class phpbb_style } $this->provider->set_styles($paths); - $this->locator->set_paths($this->provider); $new_paths = array(); foreach ($paths as $path) @@ -168,12 +159,6 @@ class phpbb_style $this->names = $names; $this->provider->set_styles($paths); - $this->locator->set_paths($this->provider); - - if ($template_path !== false) - { - $this->locator->set_template_path($template_path); - } $new_paths = array(); foreach ($paths as $path) @@ -210,32 +195,4 @@ class phpbb_style { $this->provider->set_ext_dir_prefix($ext_dir_prefix); } - - /** - * Locates source file path, accounting for styles tree and verifying that - * the path exists. - * - * @param string or array $files List of files to locate. If there is only - * one file, $files can be a string to make code easier to read. - * @param bool $return_default Determines what to return if file does not - * exist. If true, function will return location where file is - * supposed to be. If false, function will return false. - * @param bool $return_full_path If true, function will return full path - * to file. If false, function will return file name. This - * parameter can be used to check which one of set of files - * is available. - * @return string or boolean Source file path if file exists or $return_default is - * true. False if file does not exist and $return_default is false - */ - public function locate($files, $return_default = false, $return_full_path = true) - { - // convert string to array - if (is_string($files)) - { - $files = array($files); - } - - // use resource locator to find files - return $this->locator->get_first_file_location($files, $return_default, $return_full_path); - } } diff --git a/phpBB/phpbb/template/locator.php b/phpBB/phpbb/template/locator.php deleted file mode 100644 index f6fd20bcc2..0000000000 --- a/phpBB/phpbb/template/locator.php +++ /dev/null @@ -1,163 +0,0 @@ - filename pairs. - * - * @param array $filename_array Should be a hash of handle => filename pairs. - */ - public function set_filenames(array $filename_array); - - /** - * Determines the filename for a template handle. - * - * The filename comes from array used in a set_filenames call, - * which should have been performed prior to invoking this function. - * Return value is a file basename (without path). - * - * @param $handle string Template handle - * @return string Filename corresponding to the template handle - */ - public function get_filename_for_handle($handle); - - /** - * Determines the source file path for a template handle without - * regard for styles tree. - * - * This function returns the path in "primary" style directory - * corresponding to the given template handle. That path may or - * may not actually exist on the filesystem. Because this function - * does not perform stat calls to determine whether the path it - * 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 - * directory if primary style has parent styles). - * - * This function will trigger an error if the handle was never - * associated with a template file via set_filenames. - * - * @param $handle string Template handle - * @return string Path to source file path in primary style directory - */ - public function get_virtual_source_file_for_handle($handle); - - /** - * Determines the source file path for a template handle, accounting - * for styles tree and verifying that the path exists. - * - * This function returns the actual path that may be compiled for - * the specified template handle. It will trigger an error if - * the template handle was never associated with a template path - * via set_filenames or if the template file does not exist on the - * filesystem. - * - * Use get_virtual_source_file_for_handle to just resolve a template - * handle to a path without any filesystem or styles tree checks. - * - * @param string $handle Template handle (i.e. "friendly" template name) - * @param bool $find_all If true, each root path will be checked and function - * will return array of files instead of string and will not - * trigger a error if template does not exist - * @return string Source file path - */ - public function get_source_file_for_handle($handle, $find_all = false); - - /** - * Obtains a complete filesystem path for a file in a style. - * - * This function traverses the style tree (selected style and - * its parents in order, if inheritance is being used) and finds - * the first file on the filesystem matching specified relative path, - * or the first of the specified paths if more than one path is given. - * - * This function can be used to determine filesystem path of any - * file under any style, with the consequence being that complete - * relative to the style directory path must be provided as an argument. - * - * In particular, this function can be used to locate templates - * and javascript files. - * - * For locating templates get_first_template_location should be used - * as it prepends the configured template path to the template basename. - * - * Note: "selected style" is whatever style the style resource locator - * is configured for. - * - * The return value then will be a path, relative to the current - * directory or absolute, to the first existing file in the selected - * style or its closest parent. - * - * If the selected style does not have the file being searched, - * (and if inheritance is involved, none of the parents have it either), - * false will be returned. - * - * Multiple files can be specified, in which case the first file in - * the list that can be found on the filesystem is returned. - * - * If multiple files are specified and inheritance is involved, - * first each of the specified files is checked in the selected style, - * then each of the specified files is checked in the immediate parent, - * etc. - * - * Specifying true for $return_default will cause the function to - * return the first path which was checked for existence in the event - * that the template file was not found, instead of false. - * This is always a path in the selected style itself, not any of its - * parents. - * - * If $return_full_path is false, then instead of returning a usable - * path (when the file is found) the file's path relative to the style - * directory will be returned. This is the same path as was given to - * the function as a parameter. This can be used to check which of the - * files specified in $files exists. Naturally this requires passing - * more than one file in $files. - * - * @param array $files List of files to locate. - * @param bool $return_default Determines what to return if file does not - * exist. If true, function will return location where file is - * supposed to be. If false, function will return false. - * @param bool $return_full_path If true, function will return full path - * to file. If false, function will return file name. This - * parameter can be used to check which one of set of files - * is available. - * @return string or boolean Source file path if file exists or $return_default is - * true. False if file does not exist and $return_default is false - */ - public function get_first_file_location($files, $return_default = false, $return_full_path = true); -} -- cgit v1.2.1 From 44a82dd0837a4693b6a4a410c21c438f244094d3 Mon Sep 17 00:00:00 2001 From: Nathaniel Guse Date: Wed, 24 Jul 2013 12:05:04 -0500 Subject: [ticket/11628] Remove style path provider No longer used since Twig was implemented. PHPBB3-11628 --- phpBB/config/services.yml | 11 --- phpBB/includes/bbcode.php | 3 +- phpBB/install/index.php | 4 +- phpBB/phpbb/style/extension_path_provider.php | 137 -------------------------- phpBB/phpbb/style/path_provider.php | 62 ------------ phpBB/phpbb/style/path_provider_interface.php | 42 -------- phpBB/phpbb/style/style.php | 25 +---- 7 files changed, 3 insertions(+), 281 deletions(-) delete mode 100644 phpBB/phpbb/style/extension_path_provider.php delete mode 100644 phpBB/phpbb/style/path_provider.php delete mode 100644 phpBB/phpbb/style/path_provider_interface.php (limited to 'phpBB') diff --git a/phpBB/config/services.yml b/phpBB/config/services.yml index 4902f4b6f6..7acc44f24d 100644 --- a/phpBB/config/services.yml +++ b/phpBB/config/services.yml @@ -258,19 +258,8 @@ services: - %core.php_ext% - @config - @user - - @style.path_provider_ext - @template - style.path_provider_ext: - class: phpbb_style_extension_path_provider - arguments: - - @ext.manager - - @style.path_provider - - %core.root_path% - - style.path_provider: - class: phpbb_style_path_provider - template: class: phpbb_template_twig arguments: diff --git a/phpBB/includes/bbcode.php b/phpBB/includes/bbcode.php index 4ce6f17d90..9b1939030a 100644 --- a/phpBB/includes/bbcode.php +++ b/phpBB/includes/bbcode.php @@ -132,9 +132,8 @@ class bbcode { $this->template_bitfield = new bitfield($user->style['bbcode_bitfield']); - $style_path_provider = new phpbb_style_extension_path_provider($phpbb_extension_manager, new phpbb_style_path_provider(), $phpbb_root_path); $template = new phpbb_template_twig($phpbb_root_path, $phpEx, $config, $user, new phpbb_template_context(), $phpbb_extension_manager); - $style = new phpbb_style($phpbb_root_path, $phpEx, $config, $user, $style_path_provider, $template); + $style = new phpbb_style($phpbb_root_path, $phpEx, $config, $user, $template); $style->set_style(); $template->set_filenames(array('bbcode.html' => 'bbcode.html')); $this->template_filename = $template->get_source_file_for_handle('bbcode.html'); diff --git a/phpBB/install/index.php b/phpBB/install/index.php index f924b05547..46660723ba 100644 --- a/phpBB/install/index.php +++ b/phpBB/install/index.php @@ -212,10 +212,8 @@ $config = new phpbb_config(array( 'load_tplcompile' => '1' )); -$phpbb_style_path_provider = new phpbb_style_path_provider(); $template = new phpbb_template_twig($phpbb_root_path, $phpEx, $config, $user, new phpbb_template_context()); -$phpbb_style = new phpbb_style($phpbb_root_path, $phpEx, $config, $user, $phpbb_style_path_provider, $template); -$phpbb_style->set_ext_dir_prefix('adm/'); +$phpbb_style = new phpbb_style($phpbb_root_path, $phpEx, $config, $user, $template); $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/phpbb/style/extension_path_provider.php b/phpBB/phpbb/style/extension_path_provider.php deleted file mode 100644 index ec1d85f821..0000000000 --- a/phpBB/phpbb/style/extension_path_provider.php +++ /dev/null @@ -1,137 +0,0 @@ -base_path_provider = $base_path_provider; - $this->phpbb_root_path = $phpbb_root_path; - } - - /** - * Sets a prefix for style paths searched within extensions. - * - * The prefix is inserted between the extension's path e.g. ext/foo/ and - * the looked up style path, e.g. styles/bar/. So it should not have a - * leading slash, but should have a trailing slash. - * - * @param string $ext_dir_prefix The prefix including trailing slash - * @return null - */ - public function set_ext_dir_prefix($ext_dir_prefix) - { - $this->ext_dir_prefix = $ext_dir_prefix; - } - - /** - * Finds style paths using the extension manager - * - * Locates a path (e.g. styles/prosilver/) in all active extensions. - * Then appends the core style paths based in the current working - * directory. - * - * @return array List of style paths - */ - public function find() - { - $directories = array(); - - $finder = $this->extension_manager->get_finder(); - foreach ($this->base_path_provider as $key => $paths) - { - if ($key == 'style') - { - foreach ($paths as $path) - { - $directories['style'][] = $path; - if ($path && !phpbb_is_absolute($path)) - { - // Remove phpBB root path from the style path, - // so the finder is able to find extension styles, - // when the root path is not ./ - if (strpos($path, $this->phpbb_root_path) === 0) - { - $path = substr($path, strlen($this->phpbb_root_path)); - } - - $result = $finder->directory('/' . $this->ext_dir_prefix . $path) - ->get_directories(true, false, true); - foreach ($result as $ext => $ext_path) - { - // Make sure $ext_path has no ending slash - if (substr($ext_path, -1) === '/') - { - $ext_path = substr($ext_path, 0, -1); - } - $directories[$ext][] = $ext_path; - } - } - } - } - } - - return $directories; - } - - /** - * Overwrites the current style paths - * - * @param array $styles An array of style paths. The first element is the main style. - * @return null - */ - public function set_styles(array $styles) - { - $this->base_path_provider->set_styles($styles); - $this->items = null; - } -} diff --git a/phpBB/phpbb/style/path_provider.php b/phpBB/phpbb/style/path_provider.php deleted file mode 100644 index 731d682e88..0000000000 --- a/phpBB/phpbb/style/path_provider.php +++ /dev/null @@ -1,62 +0,0 @@ -paths = array('style' => $styles); - } - - /** - * Retrieve an iterator over all style paths - * - * @return ArrayIterator An iterator for the array of style paths - */ - public function getIterator() - { - return new ArrayIterator($this->paths); - } -} diff --git a/phpBB/phpbb/style/path_provider_interface.php b/phpBB/phpbb/style/path_provider_interface.php deleted file mode 100644 index 1a6153a4d3..0000000000 --- a/phpBB/phpbb/style/path_provider_interface.php +++ /dev/null @@ -1,42 +0,0 @@ -phpbb_root_path = $phpbb_root_path; $this->php_ext = $php_ext; $this->config = $config; $this->user = $user; - $this->provider = $provider; $this->template = $template; } @@ -121,8 +113,6 @@ class phpbb_style } } - $this->provider->set_styles($paths); - $new_paths = array(); foreach ($paths as $path) { @@ -158,8 +148,6 @@ class phpbb_style } $this->names = $names; - $this->provider->set_styles($paths); - $new_paths = array(); foreach ($paths as $path) { @@ -184,15 +172,4 @@ class phpbb_style { return $this->phpbb_root_path . trim($style_base_directory, '/') . '/' . $path; } - - /** - * Defines a prefix to use for style paths in extensions - * - * @param string $ext_dir_prefix The prefix including trailing slash - * @return null - */ - public function set_ext_dir_prefix($ext_dir_prefix) - { - $this->provider->set_ext_dir_prefix($ext_dir_prefix); - } } -- cgit v1.2.1 From e80503696350d7410e82e63e9d108d65e3c35696 Mon Sep 17 00:00:00 2001 From: Oliver Schramm Date: Wed, 24 Jul 2013 19:08:49 +0200 Subject: [ticket/10917] Using phpbb wrapper PHPBB3-10917 --- phpBB/install/install_update.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB') diff --git a/phpBB/install/install_update.php b/phpBB/install/install_update.php index 5393040061..5419e3edf1 100644 --- a/phpBB/install/install_update.php +++ b/phpBB/install/install_update.php @@ -204,7 +204,7 @@ class install_update extends module } // Check if the update files stored are for the latest version... - if (version_compare(strtolower($this->latest_version), strtolower($this->update_info['version']['to']), '>')) + if (phpbb_version_compare($this->latest_version, $this->update_info['version']['to'], '>')) { $this->unequal_version = true; -- cgit v1.2.1 From 5d1afb453211d42a8deacb66684c136385918192 Mon Sep 17 00:00:00 2001 From: Nathaniel Guse Date: Wed, 24 Jul 2013 12:24:35 -0500 Subject: [ticket/11628] Remove phpbb_style (move methods to phpbb_template) PHPBB3-11628 --- phpBB/adm/index.php | 2 +- phpBB/adm/swatch.php | 2 +- phpBB/common.php | 1 - phpBB/config/services.yml | 11 +-- phpBB/includes/bbcode.php | 3 +- phpBB/includes/functions_module.php | 6 +- phpBB/install/index.php | 3 +- phpBB/install/install_update.php | 4 +- phpBB/phpbb/controller/resolver.php | 16 ++-- phpBB/phpbb/style/style.php | 175 ------------------------------------ phpBB/phpbb/template/template.php | 30 +++++++ phpBB/phpbb/template/twig/twig.php | 107 +++++++++++++++++++++- phpBB/phpbb/user.php | 4 +- 13 files changed, 156 insertions(+), 208 deletions(-) delete mode 100644 phpBB/phpbb/style/style.php (limited to 'phpBB') diff --git a/phpBB/adm/index.php b/phpBB/adm/index.php index 8cd1967c75..3f29072899 100644 --- a/phpBB/adm/index.php +++ b/phpBB/adm/index.php @@ -50,7 +50,7 @@ $module_id = request_var('i', ''); $mode = request_var('mode', ''); // Set custom style for admin area -$phpbb_style->set_custom_style('admin', $phpbb_admin_path . 'style', array(), ''); +$template->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/swatch.php b/phpBB/adm/swatch.php index 3ae38d0d8b..70441ffeed 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', array(), ''); +$template->set_custom_style('admin', $phpbb_admin_path . 'style', array(), ''); $template->set_filenames(array( 'body' => 'colour_swatch.html') diff --git a/phpBB/common.php b/phpBB/common.php index 962a1f951f..6a1f307d64 100644 --- a/phpBB/common.php +++ b/phpBB/common.php @@ -121,7 +121,6 @@ $phpbb_extension_manager = $phpbb_container->get('ext.manager'); $phpbb_subscriber_loader = $phpbb_container->get('event.subscriber_loader'); $template = $phpbb_container->get('template'); -$phpbb_style = $phpbb_container->get('style'); // Add own hook handler require($phpbb_root_path . 'includes/hooks/index.' . $phpEx); diff --git a/phpBB/config/services.yml b/phpBB/config/services.yml index 7acc44f24d..d0753322da 100644 --- a/phpBB/config/services.yml +++ b/phpBB/config/services.yml @@ -98,7 +98,7 @@ services: arguments: - @user - @service_container - - @style + - @template cron.task_collection: class: phpbb_di_service_collection @@ -251,15 +251,6 @@ services: request: class: phpbb_request - style: - class: phpbb_style - arguments: - - %core.root_path% - - %core.php_ext% - - @config - - @user - - @template - template: class: phpbb_template_twig arguments: diff --git a/phpBB/includes/bbcode.php b/phpBB/includes/bbcode.php index 9b1939030a..2fa6a8b099 100644 --- a/phpBB/includes/bbcode.php +++ b/phpBB/includes/bbcode.php @@ -133,8 +133,7 @@ class bbcode $this->template_bitfield = new bitfield($user->style['bbcode_bitfield']); $template = new phpbb_template_twig($phpbb_root_path, $phpEx, $config, $user, new phpbb_template_context(), $phpbb_extension_manager); - $style = new phpbb_style($phpbb_root_path, $phpEx, $config, $user, $template); - $style->set_style(); + $template->set_style(); $template->set_filenames(array('bbcode.html' => 'bbcode.html')); $this->template_filename = $template->get_source_file_for_handle('bbcode.html'); } diff --git a/phpBB/includes/functions_module.php b/phpBB/includes/functions_module.php index 99c24fcb19..a5ece1ecac 100644 --- a/phpBB/includes/functions_module.php +++ b/phpBB/includes/functions_module.php @@ -455,7 +455,7 @@ class p_master */ function load_active($mode = false, $module_url = false, $execute_module = true) { - global $phpbb_root_path, $phpbb_admin_path, $phpEx, $user, $phpbb_style; + global $phpbb_root_path, $phpbb_admin_path, $phpEx, $user, $template; $module_path = $this->include_path . $this->p_class; $icat = request_var('icat', ''); @@ -508,7 +508,7 @@ class p_master if (is_dir($module_style_dir)) { - $phpbb_style->set_custom_style('admin', array($module_style_dir, $phpbb_admin_path . 'style'), array(), ''); + $template->set_custom_style('admin', array($module_style_dir, $phpbb_admin_path . 'style'), array(), ''); } } @@ -537,7 +537,7 @@ class p_master if (is_dir($phpbb_root_path . $module_style_dir)) { - $phpbb_style->set_style(array($module_style_dir, 'styles')); + $template->set_style(array($module_style_dir, 'styles')); } } diff --git a/phpBB/install/index.php b/phpBB/install/index.php index 46660723ba..fd0d8a2d48 100644 --- a/phpBB/install/index.php +++ b/phpBB/install/index.php @@ -213,8 +213,7 @@ $config = new phpbb_config(array( )); $template = new phpbb_template_twig($phpbb_root_path, $phpEx, $config, $user, new phpbb_template_context()); -$phpbb_style = new phpbb_style($phpbb_root_path, $phpEx, $config, $user, $template); -$phpbb_style->set_custom_style('admin', $phpbb_admin_path . 'style', array(), ''); +$template->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_update.php b/phpBB/install/install_update.php index df9b6c1c7e..51fbd1975c 100644 --- a/phpBB/install/install_update.php +++ b/phpBB/install/install_update.php @@ -71,7 +71,7 @@ class install_update extends module function main($mode, $sub) { - global $phpbb_style, $template, $phpEx, $phpbb_root_path, $user, $db, $config, $cache, $auth, $language; + global $template, $phpEx, $phpbb_root_path, $user, $db, $config, $cache, $auth, $language; global $request, $phpbb_admin_path, $phpbb_adm_relative_path, $phpbb_container; // Create a normal container now @@ -138,7 +138,7 @@ class install_update extends module } // Set custom template again. ;) - $phpbb_style->set_custom_style('admin', $phpbb_admin_path . 'style', array(), ''); + $template->set_custom_style('admin', $phpbb_admin_path . 'style', array(), ''); $template->assign_vars(array( 'S_USER_LANG' => $user->lang['USER_LANG'], diff --git a/phpBB/phpbb/controller/resolver.php b/phpBB/phpbb/controller/resolver.php index 95dfc8da8e..850df84a0f 100644 --- a/phpBB/phpbb/controller/resolver.php +++ b/phpBB/phpbb/controller/resolver.php @@ -38,23 +38,23 @@ class phpbb_controller_resolver implements ControllerResolverInterface protected $container; /** - * phpbb_style object - * @var phpbb_style + * phpbb_template object + * @var phpbb_template */ - protected $style; + protected $template; /** * Construct method * * @param phpbb_user $user User Object * @param ContainerInterface $container ContainerInterface object - * @param phpbb_style $style + * @param phpbb_template_interface $template */ - public function __construct(phpbb_user $user, ContainerInterface $container, phpbb_style $style = null) + public function __construct(phpbb_user $user, ContainerInterface $container, phpbb_template $template = null) { $this->user = $user; $this->container = $container; - $this->style = $style; + $this->template = $template; } /** @@ -96,13 +96,13 @@ class phpbb_controller_resolver implements ControllerResolverInterface $controller_dir = explode('_', get_class($controller_object)); // 0 phpbb, 1 ext, 2 vendor, 3 extension name, ... - if (!is_null($this->style) && isset($controller_dir[3]) && $controller_dir[1] === 'ext') + if (!is_null($this->template) && isset($controller_dir[3]) && $controller_dir[1] === 'ext') { $controller_style_dir = 'ext/' . $controller_dir[2] . '/' . $controller_dir[3] . '/styles'; if (is_dir($controller_style_dir)) { - $this->style->set_style(array($controller_style_dir, 'styles')); + $this->template->set_style(array($controller_style_dir, 'styles')); } } diff --git a/phpBB/phpbb/style/style.php b/phpBB/phpbb/style/style.php deleted file mode 100644 index 283e3015ca..0000000000 --- a/phpBB/phpbb/style/style.php +++ /dev/null @@ -1,175 +0,0 @@ -phpbb_root_path = $phpbb_root_path; - $this->php_ext = $php_ext; - $this->config = $config; - $this->user = $user; - $this->template = $template; - } - - /** - * Get the style tree of the style preferred by the current user - * - * @return array Style tree, most specific first - */ - public function get_user_style() - { - $style_list = array( - $this->user->style['style_path'], - ); - - if ($this->user->style['style_parent_id']) - { - $style_list = array_merge($style_list, array_reverse(explode('/', $this->user->style['style_parent_tree']))); - } - - return $style_list; - } - - /** - * Set style location based on (current) user's chosen style. - * - * @param array $style_directories The directories to add style paths for - * E.g. array('ext/foo/bar/styles', 'styles') - * Default: array('styles') (phpBB's style directory) - * @return bool true - */ - public function set_style($style_directories = array('styles')) - { - $this->names = $this->get_user_style(); - - $paths = array(); - foreach ($style_directories as $directory) - { - foreach ($this->names as $name) - { - $path = $this->get_style_path($name, $directory); - - if (is_dir($path)) - { - $paths[] = $path; - } - } - } - - $new_paths = array(); - foreach ($paths as $path) - { - $new_paths[] = $path . '/template/'; - } - - $this->template->set_style_names($this->names, $new_paths, ($style_directories === array('styles'))); - - return true; - } - - /** - * Set custom style location (able to use directory outside of phpBB). - * - * Note: Templates are still compiled to phpBB's cache directory. - * - * @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/). - * @return bool true - */ - 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; - - $new_paths = array(); - foreach ($paths as $path) - { - $new_paths[] = $path . '/' . (($template_path !== false) ? $template_path : 'template/'); - } - - $this->template->set_style_names($names, $new_paths); - - return true; - } - - /** - * Get location of style directory for specific style_path - * - * @param string $path Style path, such as "prosilver" - * @param string $style_base_directory The base directory the style is in - * E.g. 'styles', 'ext/foo/bar/styles' - * Default: 'styles' - * @return string Path to style directory, relative to current path - */ - public function get_style_path($path, $style_base_directory = 'styles') - { - return $this->phpbb_root_path . trim($style_base_directory, '/') . '/' . $path; - } -} diff --git a/phpBB/phpbb/template/template.php b/phpBB/phpbb/template/template.php index 89a01e924d..537c4eaf01 100644 --- a/phpBB/phpbb/template/template.php +++ b/phpBB/phpbb/template/template.php @@ -33,6 +33,36 @@ interface phpbb_template */ public function set_filenames(array $filename_array); + /** + * Get the style tree of the style preferred by the current user + * + * @return array Style tree, most specific first + */ + public function get_user_style(); + + /** + * Set style location based on (current) user's chosen style. + * + * @param array $style_directories The directories to add style paths for + * E.g. array('ext/foo/bar/styles', 'styles') + * Default: array('styles') (phpBB's style directory) + * @return bool true + */ + public function set_style($style_directories = array('styles')); + + /** + * Set custom style location (able to use directory outside of phpBB). + * + * Note: Templates are still compiled to phpBB's cache directory. + * + * @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/). + * @return bool true + */ + public function set_custom_style($name, $paths, $names = array(), $template_path = false); + /** * Sets the style names/paths corresponding to style hierarchy being compiled * and/or rendered. diff --git a/phpBB/phpbb/template/twig/twig.php b/phpBB/phpbb/template/twig/twig.php index 92a37d1634..92a52d26b8 100644 --- a/phpBB/phpbb/template/twig/twig.php +++ b/phpBB/phpbb/template/twig/twig.php @@ -177,6 +177,97 @@ class phpbb_template_twig implements phpbb_template return $this; } + /** + * Get the style tree of the style preferred by the current user + * + * @return array Style tree, most specific first + */ + public function get_user_style() + { + $style_list = array( + $this->user->style['style_path'], + ); + + if ($this->user->style['style_parent_id']) + { + $style_list = array_merge($style_list, array_reverse(explode('/', $this->user->style['style_parent_tree']))); + } + + return $style_list; + } + + /** + * Set style location based on (current) user's chosen style. + * + * @param array $style_directories The directories to add style paths for + * E.g. array('ext/foo/bar/styles', 'styles') + * Default: array('styles') (phpBB's style directory) + * @return bool true + */ + public function set_style($style_directories = array('styles')) + { + $this->names = $this->get_user_style(); + + $paths = array(); + foreach ($style_directories as $directory) + { + foreach ($this->names as $name) + { + $path = $this->get_style_path($name, $directory); + + if (is_dir($path)) + { + $paths[] = $path; + } + } + } + + $new_paths = array(); + foreach ($paths as $path) + { + $new_paths[] = $path . '/template/'; + } + + $this->set_style_names($this->names, $new_paths, ($style_directories === array('styles'))); + + return true; + } + + /** + * Set custom style location (able to use directory outside of phpBB). + * + * Note: Templates are still compiled to phpBB's cache directory. + * + * @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/). + * @return bool true + */ + 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; + + $new_paths = array(); + foreach ($paths as $path) + { + $new_paths[] = $path . '/' . (($template_path !== false) ? $template_path : 'template/'); + } + + $this->set_style_names($names, $new_paths); + + return true; + } + /** * Sets the style names/paths corresponding to style hierarchy being compiled * and/or rendered. @@ -194,7 +285,7 @@ class phpbb_template_twig implements phpbb_template // Set as __main__ namespace $this->twig->getLoader()->setPaths($style_paths); - // Core style namespace from phpbb_style::set_style() + // Core style namespace from this::set_style() if ($is_core) { $this->twig->getLoader()->setPaths($style_paths, 'core'); @@ -462,4 +553,18 @@ class phpbb_template_twig implements phpbb_template { return $this->twig->getLoader()->getCacheKey($this->get_filename_from_handle($handle)); } + + /** + * Get location of style directory for specific style_path + * + * @param string $path Style path, such as "prosilver" + * @param string $style_base_directory The base directory the style is in + * E.g. 'styles', 'ext/foo/bar/styles' + * Default: 'styles' + * @return string Path to style directory, relative to current path + */ + protected function get_style_path($path, $style_base_directory = 'styles') + { + return $this->phpbb_root_path . trim($style_base_directory, '/') . '/' . $path; + } } diff --git a/phpBB/phpbb/user.php b/phpBB/phpbb/user.php index 5530fe3f03..2828bab6a8 100644 --- a/phpBB/phpbb/user.php +++ b/phpBB/phpbb/user.php @@ -75,7 +75,7 @@ class phpbb_user extends phpbb_session */ function setup($lang_set = false, $style_id = false) { - global $db, $phpbb_style, $template, $config, $auth, $phpEx, $phpbb_root_path, $cache; + global $db, $template, $config, $auth, $phpEx, $phpbb_root_path, $cache; global $phpbb_dispatcher; if ($this->data['user_id'] != ANONYMOUS) @@ -236,7 +236,7 @@ class phpbb_user extends phpbb_session } } - $phpbb_style->set_style(); + $template->set_style(); $this->img_lang = $this->lang_name; -- cgit v1.2.1 From 85ff05bec635e8e6ef0fb64a561e2dd400b53897 Mon Sep 17 00:00:00 2001 From: Nathaniel Guse Date: Wed, 24 Jul 2013 12:34:22 -0500 Subject: [ticket/11628] Remove $this->names, $this->style_names from phpbb_template These are not used anywhere PHPBB3-11628 --- phpBB/phpbb/template/twig/twig.php | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) (limited to 'phpBB') diff --git a/phpBB/phpbb/template/twig/twig.php b/phpBB/phpbb/template/twig/twig.php index 92a52d26b8..4ae918ed86 100644 --- a/phpBB/phpbb/template/twig/twig.php +++ b/phpBB/phpbb/template/twig/twig.php @@ -74,16 +74,6 @@ class phpbb_template_twig implements phpbb_template */ protected $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 - */ - protected $style_names; - /** * Twig Environment * @@ -206,12 +196,12 @@ class phpbb_template_twig implements phpbb_template */ public function set_style($style_directories = array('styles')) { - $this->names = $this->get_user_style(); + $names = $this->get_user_style(); $paths = array(); foreach ($style_directories as $directory) { - foreach ($this->names as $name) + foreach ($names as $name) { $path = $this->get_style_path($name, $directory); @@ -228,7 +218,7 @@ class phpbb_template_twig implements phpbb_template $new_paths[] = $path . '/template/'; } - $this->set_style_names($this->names, $new_paths, ($style_directories === array('styles'))); + $this->set_style_names($names, $new_paths, ($style_directories === array('styles'))); return true; } @@ -255,7 +245,6 @@ class phpbb_template_twig implements phpbb_template { $names = array($name); } - $this->names = $names; $new_paths = array(); foreach ($paths as $path) @@ -280,8 +269,6 @@ class phpbb_template_twig implements phpbb_template */ public function set_style_names(array $style_names, array $style_paths, $is_core = false) { - $this->style_names = $style_names; - // Set as __main__ namespace $this->twig->getLoader()->setPaths($style_paths); -- cgit v1.2.1 From ecaed319ab46ee4dd45fe788a05aaeff96afc1d2 Mon Sep 17 00:00:00 2001 From: Nathaniel Guse Date: Wed, 24 Jul 2013 12:42:37 -0500 Subject: [ticket/11628] Move setting core namespace to set_style() PHPBB3-11628 --- phpBB/phpbb/template/twig/twig.php | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) (limited to 'phpBB') diff --git a/phpBB/phpbb/template/twig/twig.php b/phpBB/phpbb/template/twig/twig.php index 4ae918ed86..eb84da20dc 100644 --- a/phpBB/phpbb/template/twig/twig.php +++ b/phpBB/phpbb/template/twig/twig.php @@ -196,6 +196,12 @@ class phpbb_template_twig implements phpbb_template */ public function set_style($style_directories = array('styles')) { + if ($style_directories !== array('styles') && $this->twig->getLoader()->getPaths('core') === array()) + { + // We should set up the core styles path since not already setup + $this->set_style(); + } + $names = $this->get_user_style(); $paths = array(); @@ -203,7 +209,7 @@ class phpbb_template_twig implements phpbb_template { foreach ($names as $name) { - $path = $this->get_style_path($name, $directory); + $path = $this->get_style_path($name, $directory) . 'template/'; if (is_dir($path)) { @@ -212,13 +218,15 @@ class phpbb_template_twig implements phpbb_template } } - $new_paths = array(); - foreach ($paths as $path) + // If we're setting up the main phpBB styles directory and the core + // namespace isn't setup yet, we will set it up now + if ($style_directories === array('styles') && $this->twig->getLoader()->getPaths('core') === array()) { - $new_paths[] = $path . '/template/'; + // Set up the core style paths namespace + $this->twig->getLoader()->setPaths($paths, 'core'); } - $this->set_style_names($names, $new_paths, ($style_directories === array('styles'))); + $this->set_style_names($names, $paths); return true; } @@ -263,21 +271,13 @@ class phpbb_template_twig implements phpbb_template * * @param array $style_names List of style names in inheritance tree order * @param array $style_paths List of style paths in inheritance tree order - * @param bool $is_core True if the style names are the "core" styles for this page load - * Core means the main phpBB template files * @return phpbb_template $this */ - public function set_style_names(array $style_names, array $style_paths, $is_core = false) + public function set_style_names(array $style_names, array $style_paths) { // Set as __main__ namespace $this->twig->getLoader()->setPaths($style_paths); - // Core style namespace from this::set_style() - if ($is_core) - { - $this->twig->getLoader()->setPaths($style_paths, 'core'); - } - // Add admin namespace if (is_dir($this->phpbb_root_path . $this->adm_relative_path . 'style/')) { @@ -552,6 +552,6 @@ class phpbb_template_twig implements phpbb_template */ protected function get_style_path($path, $style_base_directory = 'styles') { - return $this->phpbb_root_path . trim($style_base_directory, '/') . '/' . $path; + return $this->phpbb_root_path . trim($style_base_directory, '/') . '/' . rtrim($path, '/') . '/'; } } -- cgit v1.2.1 From bfbc7aa74250ea5e9e39efc63caf7cfdb9407e14 Mon Sep 17 00:00:00 2001 From: Nathaniel Guse Date: Wed, 24 Jul 2013 12:45:35 -0500 Subject: [ticket/11628] Return $this from set_style, set_custom_style PHPBB3-11628 --- phpBB/phpbb/template/template.php | 4 ++-- phpBB/phpbb/template/twig/twig.php | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) (limited to 'phpBB') diff --git a/phpBB/phpbb/template/template.php b/phpBB/phpbb/template/template.php index 537c4eaf01..95a48ba0ad 100644 --- a/phpBB/phpbb/template/template.php +++ b/phpBB/phpbb/template/template.php @@ -46,7 +46,7 @@ interface phpbb_template * @param array $style_directories The directories to add style paths for * E.g. array('ext/foo/bar/styles', 'styles') * Default: array('styles') (phpBB's style directory) - * @return bool true + * @return phpbb_template $this */ public function set_style($style_directories = array('styles')); @@ -59,7 +59,7 @@ interface phpbb_template * @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/). - * @return bool true + * @return phpbb_template $this */ public function set_custom_style($name, $paths, $names = array(), $template_path = false); diff --git a/phpBB/phpbb/template/twig/twig.php b/phpBB/phpbb/template/twig/twig.php index eb84da20dc..48ea8edeb1 100644 --- a/phpBB/phpbb/template/twig/twig.php +++ b/phpBB/phpbb/template/twig/twig.php @@ -192,7 +192,7 @@ class phpbb_template_twig implements phpbb_template * @param array $style_directories The directories to add style paths for * E.g. array('ext/foo/bar/styles', 'styles') * Default: array('styles') (phpBB's style directory) - * @return bool true + * @return phpbb_template $this */ public function set_style($style_directories = array('styles')) { @@ -228,7 +228,7 @@ class phpbb_template_twig implements phpbb_template $this->set_style_names($names, $paths); - return true; + return $this; } /** @@ -240,7 +240,7 @@ class phpbb_template_twig implements phpbb_template * @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/). - * @return bool true + * @return phpbb_template $this */ public function set_custom_style($name, $paths, $names = array(), $template_path = false) { @@ -262,7 +262,7 @@ class phpbb_template_twig implements phpbb_template $this->set_style_names($names, $new_paths); - return true; + return $this; } /** -- cgit v1.2.1 From 4b761f65758c40db4851983fa3a08d354da3323d Mon Sep 17 00:00:00 2001 From: Nathaniel Guse Date: Wed, 24 Jul 2013 12:55:41 -0500 Subject: [ticket/11628] Remove third parameter ($names) from set_custom_style This was basically duplicating functionality. $names would be used if not empty, else array($name) would be used. Merged functionality into the first argument PHPBB3-11628 --- phpBB/adm/index.php | 2 +- phpBB/adm/swatch.php | 2 +- phpBB/includes/functions_module.php | 2 +- phpBB/install/index.php | 2 +- phpBB/install/install_update.php | 2 +- phpBB/phpbb/template/template.php | 5 ++--- phpBB/phpbb/template/twig/twig.php | 9 ++++----- 7 files changed, 11 insertions(+), 13 deletions(-) (limited to 'phpBB') diff --git a/phpBB/adm/index.php b/phpBB/adm/index.php index 3f29072899..c79327d22c 100644 --- a/phpBB/adm/index.php +++ b/phpBB/adm/index.php @@ -50,7 +50,7 @@ $module_id = request_var('i', ''); $mode = request_var('mode', ''); // Set custom style for admin area -$template->set_custom_style('admin', $phpbb_admin_path . 'style', array(), ''); +$template->set_custom_style('admin', $phpbb_admin_path . 'style', ''); $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/swatch.php b/phpBB/adm/swatch.php index 70441ffeed..ef89081dc8 100644 --- a/phpBB/adm/swatch.php +++ b/phpBB/adm/swatch.php @@ -22,7 +22,7 @@ $auth->acl($user->data); $user->setup(); // Set custom template for admin area -$template->set_custom_style('admin', $phpbb_admin_path . 'style', array(), ''); +$template->set_custom_style('admin', $phpbb_admin_path . 'style', ''); $template->set_filenames(array( 'body' => 'colour_swatch.html') diff --git a/phpBB/includes/functions_module.php b/phpBB/includes/functions_module.php index a5ece1ecac..c84e02afe6 100644 --- a/phpBB/includes/functions_module.php +++ b/phpBB/includes/functions_module.php @@ -508,7 +508,7 @@ class p_master if (is_dir($module_style_dir)) { - $template->set_custom_style('admin', array($module_style_dir, $phpbb_admin_path . 'style'), array(), ''); + $template->set_custom_style('admin', array($module_style_dir, $phpbb_admin_path . 'style'), ''); } } diff --git a/phpBB/install/index.php b/phpBB/install/index.php index fd0d8a2d48..f80b975e2c 100644 --- a/phpBB/install/index.php +++ b/phpBB/install/index.php @@ -213,7 +213,7 @@ $config = new phpbb_config(array( )); $template = new phpbb_template_twig($phpbb_root_path, $phpEx, $config, $user, new phpbb_template_context()); -$template->set_custom_style('admin', $phpbb_admin_path . 'style', array(), ''); +$template->set_custom_style('admin', $phpbb_admin_path . 'style', ''); $template->assign_var('T_ASSETS_PATH', '../assets'); $template->assign_var('T_TEMPLATE_PATH', $phpbb_admin_path . 'style'); diff --git a/phpBB/install/install_update.php b/phpBB/install/install_update.php index 51fbd1975c..63f10c96d7 100644 --- a/phpBB/install/install_update.php +++ b/phpBB/install/install_update.php @@ -138,7 +138,7 @@ class install_update extends module } // Set custom template again. ;) - $template->set_custom_style('admin', $phpbb_admin_path . 'style', array(), ''); + $template->set_custom_style('admin', $phpbb_admin_path . 'style', ''); $template->assign_vars(array( 'S_USER_LANG' => $user->lang['USER_LANG'], diff --git a/phpBB/phpbb/template/template.php b/phpBB/phpbb/template/template.php index 95a48ba0ad..c77586587f 100644 --- a/phpBB/phpbb/template/template.php +++ b/phpBB/phpbb/template/template.php @@ -55,13 +55,12 @@ interface phpbb_template * * Note: Templates are still compiled to phpBB's cache directory. * - * @param string $name Name of style, used for cache prefix. Examples: "admin", "prosilver" + * @param string|array $names Array of names or string of name of template(s) in inheritance tree order, used by extensions. * @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/). * @return phpbb_template $this */ - public function set_custom_style($name, $paths, $names = array(), $template_path = false); + public function set_custom_style($names, $paths, $template_path = false); /** * Sets the style names/paths corresponding to style hierarchy being compiled diff --git a/phpBB/phpbb/template/twig/twig.php b/phpBB/phpbb/template/twig/twig.php index 48ea8edeb1..c9249196e7 100644 --- a/phpBB/phpbb/template/twig/twig.php +++ b/phpBB/phpbb/template/twig/twig.php @@ -236,22 +236,21 @@ class phpbb_template_twig implements phpbb_template * * Note: Templates are still compiled to phpBB's cache directory. * - * @param string $name Name of style, used for cache prefix. Examples: "admin", "prosilver" + * @param string|array $names Array of names or string of name of template(s) in inheritance tree order, used by extensions. * @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/). * @return phpbb_template $this */ - public function set_custom_style($name, $paths, $names = array(), $template_path = false) + public function set_custom_style($names, $paths, $template_path = false) { if (is_string($paths)) { $paths = array($paths); } - if (empty($names)) + if (!is_array($names)) { - $names = array($name); + $names = array($names); } $new_paths = array(); -- cgit v1.2.1 From 67627f3336f7a90a7de67427d25c8cdd43d74f6e Mon Sep 17 00:00:00 2001 From: Nathaniel Guse Date: Wed, 24 Jul 2013 13:01:30 -0500 Subject: [ticket/11628] Change set_custom_style $template path to default to string Rather than default to false and compare === false ? 'template/' : value just assign this default in the arguments PHPBB3-11628 --- phpBB/phpbb/template/template.php | 4 ++-- phpBB/phpbb/template/twig/twig.php | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'phpBB') diff --git a/phpBB/phpbb/template/template.php b/phpBB/phpbb/template/template.php index c77586587f..c929934376 100644 --- a/phpBB/phpbb/template/template.php +++ b/phpBB/phpbb/template/template.php @@ -57,10 +57,10 @@ interface phpbb_template * * @param string|array $names Array of names or string of name of template(s) in inheritance tree order, used by extensions. * @param array or string $paths Array of style paths, relative to current root directory - * @param string $template_path Path to templates, relative to style directory. False if path should be set to default (templates/). + * @param string $template_path Path to templates, relative to style directory. Default (template/). * @return phpbb_template $this */ - public function set_custom_style($names, $paths, $template_path = false); + public function set_custom_style($names, $paths, $template_path = 'template/'); /** * Sets the style names/paths corresponding to style hierarchy being compiled diff --git a/phpBB/phpbb/template/twig/twig.php b/phpBB/phpbb/template/twig/twig.php index c9249196e7..5537b1195c 100644 --- a/phpBB/phpbb/template/twig/twig.php +++ b/phpBB/phpbb/template/twig/twig.php @@ -238,10 +238,10 @@ class phpbb_template_twig implements phpbb_template * * @param string|array $names Array of names or string of name of template(s) in inheritance tree order, used by extensions. * @param array or string $paths Array of style paths, relative to current root directory - * @param string $template_path Path to templates, relative to style directory. False if path should be set to default (templates/). + * @param string $template_path Path to templates, relative to style directory. Default (template/). * @return phpbb_template $this */ - public function set_custom_style($names, $paths, $template_path = false) + public function set_custom_style($names, $paths, $template_path = 'template/') { if (is_string($paths)) { @@ -256,7 +256,7 @@ class phpbb_template_twig implements phpbb_template $new_paths = array(); foreach ($paths as $path) { - $new_paths[] = $path . '/' . (($template_path !== false) ? $template_path : 'template/'); + $new_paths[] = $path . '/' . ltrim($template_path, '/'); } $this->set_style_names($names, $new_paths); -- cgit v1.2.1 From 44142782095f4a847e575dde40faef867c704220 Mon Sep 17 00:00:00 2001 From: Nathaniel Guse Date: Wed, 24 Jul 2013 13:04:27 -0500 Subject: [ticket/11628] Set admin namespace in the constructor PHPBB3-11628 --- phpBB/phpbb/template/twig/twig.php | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) (limited to 'phpBB') diff --git a/phpBB/phpbb/template/twig/twig.php b/phpBB/phpbb/template/twig/twig.php index 5537b1195c..72ba70ecc4 100644 --- a/phpBB/phpbb/template/twig/twig.php +++ b/phpBB/phpbb/template/twig/twig.php @@ -43,12 +43,6 @@ class phpbb_template_twig implements phpbb_template */ protected $phpbb_root_path; - /** - * adm relative path - * @var string - */ - protected $adm_relative_path; - /** * PHP file extension * @var string @@ -102,7 +96,6 @@ class phpbb_template_twig implements phpbb_template public function __construct($phpbb_root_path, $php_ext, $config, $user, phpbb_template_context $context, phpbb_extension_manager $extension_manager = null, $adm_relative_path = null) { $this->phpbb_root_path = $phpbb_root_path; - $this->adm_relative_path = $adm_relative_path; $this->php_ext = $php_ext; $this->config = $config; $this->user = $user; @@ -137,6 +130,12 @@ class phpbb_template_twig implements phpbb_template $lexer = new phpbb_template_twig_lexer($this->twig); $this->twig->setLexer($lexer); + + // Add admin namespace + if ($adm_relative_path !== null && is_dir($this->phpbb_root_path . $adm_relative_path . 'style/')) + { + $this->twig->getLoader()->setPaths($this->phpbb_root_path . $adm_relative_path . 'style/', 'admin'); + } } /** @@ -277,12 +276,6 @@ class phpbb_template_twig implements phpbb_template // Set as __main__ namespace $this->twig->getLoader()->setPaths($style_paths); - // Add admin namespace - if (is_dir($this->phpbb_root_path . $this->adm_relative_path . 'style/')) - { - $this->twig->getLoader()->setPaths($this->phpbb_root_path . $this->adm_relative_path . 'style/', 'admin'); - } - // Add all namespaces for all extensions if ($this->extension_manager instanceof phpbb_extension_manager) { -- cgit v1.2.1 From 863592a8bedbacf3e7bf6bee458797e819020e6f Mon Sep 17 00:00:00 2001 From: Nathaniel Guse Date: Wed, 24 Jul 2013 13:19:20 -0500 Subject: [ticket/11628] Remove set_style_names function, moved to set_custom_style PHPBB3-11628 --- phpBB/includes/functions_messenger.php | 2 +- phpBB/phpbb/template/template.php | 10 ---------- phpBB/phpbb/template/twig/twig.php | 27 ++++++--------------------- 3 files changed, 7 insertions(+), 32 deletions(-) (limited to 'phpBB') diff --git a/phpBB/includes/functions_messenger.php b/phpBB/includes/functions_messenger.php index 0222a57bcc..89dd3c70fc 100644 --- a/phpBB/includes/functions_messenger.php +++ b/phpBB/includes/functions_messenger.php @@ -660,7 +660,7 @@ class messenger { $this->setup_template(); - $this->template->set_style_names(array($path_name), $paths); + $this->template->set_custom_style($path_name, $paths, ''); } } diff --git a/phpBB/phpbb/template/template.php b/phpBB/phpbb/template/template.php index c929934376..8554365c95 100644 --- a/phpBB/phpbb/template/template.php +++ b/phpBB/phpbb/template/template.php @@ -62,16 +62,6 @@ interface phpbb_template */ public function set_custom_style($names, $paths, $template_path = 'template/'); - /** - * Sets the style names/paths corresponding to style hierarchy being compiled - * and/or rendered. - * - * @param array $style_names List of style names in inheritance tree order - * @param array $style_paths List of style paths in inheritance tree order - * @return phpbb_template $this - */ - public function set_style_names(array $style_names, array $style_paths); - /** * Clears all variables and blocks assigned to this template. * diff --git a/phpBB/phpbb/template/twig/twig.php b/phpBB/phpbb/template/twig/twig.php index 72ba70ecc4..26f454e972 100644 --- a/phpBB/phpbb/template/twig/twig.php +++ b/phpBB/phpbb/template/twig/twig.php @@ -225,7 +225,7 @@ class phpbb_template_twig implements phpbb_template $this->twig->getLoader()->setPaths($paths, 'core'); } - $this->set_style_names($names, $paths); + $this->set_custom_style($names, $paths, ''); return $this; } @@ -247,39 +247,24 @@ class phpbb_template_twig implements phpbb_template $paths = array($paths); } - if (!is_array($names)) + if (is_string($names)) { $names = array($names); } - $new_paths = array(); + $style_paths = array(); foreach ($paths as $path) { - $new_paths[] = $path . '/' . ltrim($template_path, '/'); + $style_paths[] = $path . '/' . ltrim($template_path, '/'); } - $this->set_style_names($names, $new_paths); - - return $this; - } - - /** - * Sets the style names/paths corresponding to style hierarchy being compiled - * and/or rendered. - * - * @param array $style_names List of style names in inheritance tree order - * @param array $style_paths List of style paths in inheritance tree order - * @return phpbb_template $this - */ - public function set_style_names(array $style_names, array $style_paths) - { // Set as __main__ namespace $this->twig->getLoader()->setPaths($style_paths); // Add all namespaces for all extensions if ($this->extension_manager instanceof phpbb_extension_manager) { - $style_names[] = 'all'; + $names[] = 'all'; foreach ($this->extension_manager->all_enabled() as $ext_namespace => $ext_path) { @@ -287,7 +272,7 @@ class phpbb_template_twig implements phpbb_template $namespace = str_replace('/', '_', $ext_namespace); $paths = array(); - foreach ($style_names as $style_name) + foreach ($names as $style_name) { $ext_style_path = $ext_path . 'styles/' . $style_name . '/template'; -- cgit v1.2.1 From 12c22585069066957cc3211136ebd480295d4758 Mon Sep 17 00:00:00 2001 From: Nathaniel Guse Date: Wed, 24 Jul 2013 13:25:20 -0500 Subject: [ticket/11628] Remove template_path option on set_custom_style This was set to default 'template/' to append template/ to all the paths, but every location was actually just setting it to '' to not append anything. So removed the option entirely (additional paths can be appended to the paths being sent to the function already) PHPBB3-11628 --- phpBB/adm/index.php | 2 +- phpBB/adm/swatch.php | 2 +- phpBB/includes/functions_messenger.php | 2 +- phpBB/includes/functions_module.php | 2 +- phpBB/install/index.php | 2 +- phpBB/install/install_update.php | 2 +- phpBB/phpbb/template/template.php | 3 +-- phpBB/phpbb/template/twig/twig.php | 13 +++---------- 8 files changed, 10 insertions(+), 18 deletions(-) (limited to 'phpBB') diff --git a/phpBB/adm/index.php b/phpBB/adm/index.php index c79327d22c..3520eb8b70 100644 --- a/phpBB/adm/index.php +++ b/phpBB/adm/index.php @@ -50,7 +50,7 @@ $module_id = request_var('i', ''); $mode = request_var('mode', ''); // Set custom style for admin area -$template->set_custom_style('admin', $phpbb_admin_path . 'style', ''); +$template->set_custom_style('admin', $phpbb_admin_path . 'style'); $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/swatch.php b/phpBB/adm/swatch.php index ef89081dc8..cdd6bf3969 100644 --- a/phpBB/adm/swatch.php +++ b/phpBB/adm/swatch.php @@ -22,7 +22,7 @@ $auth->acl($user->data); $user->setup(); // Set custom template for admin area -$template->set_custom_style('admin', $phpbb_admin_path . 'style', ''); +$template->set_custom_style('admin', $phpbb_admin_path . 'style'); $template->set_filenames(array( 'body' => 'colour_swatch.html') diff --git a/phpBB/includes/functions_messenger.php b/phpBB/includes/functions_messenger.php index 89dd3c70fc..3a9e1fa77b 100644 --- a/phpBB/includes/functions_messenger.php +++ b/phpBB/includes/functions_messenger.php @@ -660,7 +660,7 @@ class messenger { $this->setup_template(); - $this->template->set_custom_style($path_name, $paths, ''); + $this->template->set_custom_style($path_name, $paths); } } diff --git a/phpBB/includes/functions_module.php b/phpBB/includes/functions_module.php index c84e02afe6..8f0f6a837a 100644 --- a/phpBB/includes/functions_module.php +++ b/phpBB/includes/functions_module.php @@ -508,7 +508,7 @@ class p_master if (is_dir($module_style_dir)) { - $template->set_custom_style('admin', array($module_style_dir, $phpbb_admin_path . 'style'), ''); + $template->set_custom_style('admin', array($module_style_dir, $phpbb_admin_path . 'style')); } } diff --git a/phpBB/install/index.php b/phpBB/install/index.php index f80b975e2c..84d751e279 100644 --- a/phpBB/install/index.php +++ b/phpBB/install/index.php @@ -213,7 +213,7 @@ $config = new phpbb_config(array( )); $template = new phpbb_template_twig($phpbb_root_path, $phpEx, $config, $user, new phpbb_template_context()); -$template->set_custom_style('admin', $phpbb_admin_path . 'style', ''); +$template->set_custom_style('admin', $phpbb_admin_path . 'style'); $template->assign_var('T_ASSETS_PATH', '../assets'); $template->assign_var('T_TEMPLATE_PATH', $phpbb_admin_path . 'style'); diff --git a/phpBB/install/install_update.php b/phpBB/install/install_update.php index 63f10c96d7..a105944fb3 100644 --- a/phpBB/install/install_update.php +++ b/phpBB/install/install_update.php @@ -138,7 +138,7 @@ class install_update extends module } // Set custom template again. ;) - $template->set_custom_style('admin', $phpbb_admin_path . 'style', ''); + $template->set_custom_style('admin', $phpbb_admin_path . 'style'); $template->assign_vars(array( 'S_USER_LANG' => $user->lang['USER_LANG'], diff --git a/phpBB/phpbb/template/template.php b/phpBB/phpbb/template/template.php index 8554365c95..9881938a8f 100644 --- a/phpBB/phpbb/template/template.php +++ b/phpBB/phpbb/template/template.php @@ -57,10 +57,9 @@ interface phpbb_template * * @param string|array $names Array of names or string of name of template(s) in inheritance tree order, used by extensions. * @param array or string $paths Array of style paths, relative to current root directory - * @param string $template_path Path to templates, relative to style directory. Default (template/). * @return phpbb_template $this */ - public function set_custom_style($names, $paths, $template_path = 'template/'); + public function set_custom_style($names, $paths); /** * Clears all variables and blocks assigned to this template. diff --git a/phpBB/phpbb/template/twig/twig.php b/phpBB/phpbb/template/twig/twig.php index 26f454e972..4aa1774ef4 100644 --- a/phpBB/phpbb/template/twig/twig.php +++ b/phpBB/phpbb/template/twig/twig.php @@ -225,7 +225,7 @@ class phpbb_template_twig implements phpbb_template $this->twig->getLoader()->setPaths($paths, 'core'); } - $this->set_custom_style($names, $paths, ''); + $this->set_custom_style($names, $paths); return $this; } @@ -237,10 +237,9 @@ class phpbb_template_twig implements phpbb_template * * @param string|array $names Array of names or string of name of template(s) in inheritance tree order, used by extensions. * @param array or string $paths Array of style paths, relative to current root directory - * @param string $template_path Path to templates, relative to style directory. Default (template/). * @return phpbb_template $this */ - public function set_custom_style($names, $paths, $template_path = 'template/') + public function set_custom_style($names, $paths) { if (is_string($paths)) { @@ -252,14 +251,8 @@ class phpbb_template_twig implements phpbb_template $names = array($names); } - $style_paths = array(); - foreach ($paths as $path) - { - $style_paths[] = $path . '/' . ltrim($template_path, '/'); - } - // Set as __main__ namespace - $this->twig->getLoader()->setPaths($style_paths); + $this->twig->getLoader()->setPaths($paths); // Add all namespaces for all extensions if ($this->extension_manager instanceof phpbb_extension_manager) -- cgit v1.2.1 From 3b46f77e4e77defdd7c38249c865fdaecd83629e Mon Sep 17 00:00:00 2001 From: Nathaniel Guse Date: Wed, 24 Jul 2013 13:26:55 -0500 Subject: [ticket/11628] Shorten an if to an inline statement PHPBB3-11628 --- phpBB/phpbb/template/twig/twig.php | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) (limited to 'phpBB') diff --git a/phpBB/phpbb/template/twig/twig.php b/phpBB/phpbb/template/twig/twig.php index 4aa1774ef4..c3ed52c3bd 100644 --- a/phpBB/phpbb/template/twig/twig.php +++ b/phpBB/phpbb/template/twig/twig.php @@ -241,15 +241,8 @@ class phpbb_template_twig implements phpbb_template */ public function set_custom_style($names, $paths) { - if (is_string($paths)) - { - $paths = array($paths); - } - - if (is_string($names)) - { - $names = array($names); - } + $paths = (is_string($paths)) ? array($paths) : $paths; + $names = (is_string($names)) ? array($names) : $names; // Set as __main__ namespace $this->twig->getLoader()->setPaths($paths); -- cgit v1.2.1 From 8795a354fed4e78b64cce531e6deaec9aa4d56f8 Mon Sep 17 00:00:00 2001 From: Nathaniel Guse Date: Wed, 24 Jul 2013 13:31:09 -0500 Subject: [ticket/11628] Remove the one usage of get_style_path() Makes the code easier to follow PHPBB3-11628 --- phpBB/phpbb/template/template.php | 2 +- phpBB/phpbb/template/twig/twig.php | 18 ++---------------- 2 files changed, 3 insertions(+), 17 deletions(-) (limited to 'phpBB') diff --git a/phpBB/phpbb/template/template.php b/phpBB/phpbb/template/template.php index 9881938a8f..6b9c331a3e 100644 --- a/phpBB/phpbb/template/template.php +++ b/phpBB/phpbb/template/template.php @@ -56,7 +56,7 @@ interface phpbb_template * Note: Templates are still compiled to phpBB's cache directory. * * @param string|array $names Array of names or string of name of template(s) in inheritance tree order, used by extensions. - * @param array or string $paths Array of style paths, relative to current root directory + * @param string|array or string $paths Array of style paths, relative to current root directory * @return phpbb_template $this */ public function set_custom_style($names, $paths); diff --git a/phpBB/phpbb/template/twig/twig.php b/phpBB/phpbb/template/twig/twig.php index c3ed52c3bd..0b5b4105ae 100644 --- a/phpBB/phpbb/template/twig/twig.php +++ b/phpBB/phpbb/template/twig/twig.php @@ -208,7 +208,7 @@ class phpbb_template_twig implements phpbb_template { foreach ($names as $name) { - $path = $this->get_style_path($name, $directory) . 'template/'; + $path = $this->phpbb_root_path . trim($directory, '/') . "/{$name}/template/"; if (is_dir($path)) { @@ -236,7 +236,7 @@ class phpbb_template_twig implements phpbb_template * Note: Templates are still compiled to phpBB's cache directory. * * @param string|array $names Array of names or string of name of template(s) in inheritance tree order, used by extensions. - * @param array or string $paths Array of style paths, relative to current root directory + * @param string|array or string $paths Array of style paths, relative to current root directory * @return phpbb_template $this */ public function set_custom_style($names, $paths) @@ -503,18 +503,4 @@ class phpbb_template_twig implements phpbb_template { return $this->twig->getLoader()->getCacheKey($this->get_filename_from_handle($handle)); } - - /** - * Get location of style directory for specific style_path - * - * @param string $path Style path, such as "prosilver" - * @param string $style_base_directory The base directory the style is in - * E.g. 'styles', 'ext/foo/bar/styles' - * Default: 'styles' - * @return string Path to style directory, relative to current path - */ - protected function get_style_path($path, $style_base_directory = 'styles') - { - return $this->phpbb_root_path . trim($style_base_directory, '/') . '/' . rtrim($path, '/') . '/'; - } } -- cgit v1.2.1 From 427fa17f7fd9db6f69a6cb4634198a2f484e0bd9 Mon Sep 17 00:00:00 2001 From: Nathaniel Guse Date: Wed, 24 Jul 2013 13:34:41 -0500 Subject: [ticket/11628] Fix a bug I noticed in template->destroy Should not be setting $this->context = array()! PHPBB3-11628 --- phpBB/phpbb/template/twig/twig.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB') diff --git a/phpBB/phpbb/template/twig/twig.php b/phpBB/phpbb/template/twig/twig.php index 0b5b4105ae..710411c594 100644 --- a/phpBB/phpbb/template/twig/twig.php +++ b/phpBB/phpbb/template/twig/twig.php @@ -282,7 +282,7 @@ class phpbb_template_twig implements phpbb_template */ public function destroy() { - $this->context = array(); + $this->context->clear(); return $this; } -- cgit v1.2.1 From ffbc144a739740ad1901c9eaf481815c9ec2d918 Mon Sep 17 00:00:00 2001 From: Nathaniel Guse Date: Wed, 24 Jul 2013 13:38:12 -0500 Subject: [ticket/11628] Make get_template_vars protected Remove all references to it and the hacky code in messenger that was using it PHPBB3-11628 --- phpBB/includes/functions_messenger.php | 31 +++++++------------------------ phpBB/phpbb/template/twig/twig.php | 2 +- 2 files changed, 8 insertions(+), 25 deletions(-) (limited to 'phpBB') diff --git a/phpBB/includes/functions_messenger.php b/phpBB/includes/functions_messenger.php index 3a9e1fa77b..3bfc1a44f0 100644 --- a/phpBB/includes/functions_messenger.php +++ b/phpBB/includes/functions_messenger.php @@ -21,7 +21,7 @@ if (!defined('IN_PHPBB')) */ class messenger { - var $vars, $msg, $extra_headers, $replyto, $from, $subject; + var $msg, $extra_headers, $replyto, $from, $subject; var $addresses = array(); var $mail_priority = MAIL_NORMAL_PRIORITY; @@ -53,7 +53,7 @@ class messenger function reset() { $this->addresses = $this->extra_headers = array(); - $this->vars = $this->msg = $this->replyto = $this->from = ''; + $this->msg = $this->replyto = $this->from = ''; $this->mail_priority = MAIL_NORMAL_PRIORITY; } @@ -258,8 +258,6 @@ class messenger 'body' => $template_file . '.txt', )); - $this->vars = $this->template->get_template_vars(); - return true; } @@ -288,26 +286,11 @@ class messenger global $config, $user; // We add some standard variables we always use, no need to specify them always - if (!isset($this->vars['U_BOARD'])) - { - $this->assign_vars(array( - 'U_BOARD' => generate_board_url(), - )); - } - - if (!isset($this->vars['EMAIL_SIG'])) - { - $this->assign_vars(array( - 'EMAIL_SIG' => str_replace('
', "\n", "-- \n" . htmlspecialchars_decode($config['board_email_sig'])), - )); - } - - if (!isset($this->vars['SITENAME'])) - { - $this->assign_vars(array( - 'SITENAME' => htmlspecialchars_decode($config['sitename']), - )); - } + $this->assign_vars(array( + 'U_BOARD' => generate_board_url(), + 'EMAIL_SIG' => str_replace('
', "\n", "-- \n" . htmlspecialchars_decode($config['board_email_sig'])), + 'SITENAME' => htmlspecialchars_decode($config['sitename']), + )); // Parse message through template $this->msg = trim($this->template->assign_display('body')); diff --git a/phpBB/phpbb/template/twig/twig.php b/phpBB/phpbb/template/twig/twig.php index 710411c594..582939c252 100644 --- a/phpBB/phpbb/template/twig/twig.php +++ b/phpBB/phpbb/template/twig/twig.php @@ -464,7 +464,7 @@ class phpbb_template_twig implements phpbb_template * * @return array */ - public function get_template_vars() + protected function get_template_vars() { $context_vars = $this->context->get_data_ref(); -- cgit v1.2.1 From a9f05775025f010339fee5607df9b890abf472f8 Mon Sep 17 00:00:00 2001 From: Oliver Schramm Date: Thu, 25 Jul 2013 12:29:25 +0200 Subject: [ticket/11062] Load new strings from user's language file if provided PHPBB3-11062 --- phpBB/install/install_update.php | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'phpBB') diff --git a/phpBB/install/install_update.php b/phpBB/install/install_update.php index c18a0fb4ec..0afd6b07bb 100644 --- a/phpBB/install/install_update.php +++ b/phpBB/install/install_update.php @@ -232,6 +232,13 @@ class install_update extends module } // What about the language file? Got it updated? + if (in_array('language/' . $language . '/install.' . $phpEx, $this->update_info['files'])) + { + $lang = array(); + include($this->new_location . 'language/' . $language . '/install.' . $phpEx); + // this is the user's language.. just merge it + $user->lang = array_merge($user->lang, $lang); + } if (in_array('language/en/install.' . $phpEx, $this->update_info['files'])) { $lang = array(); -- cgit v1.2.1 From 7304ac9c3ca817716f14bb8817a201b149e5ebef Mon Sep 17 00:00:00 2001 From: Oliver Schramm Date: Thu, 25 Jul 2013 13:10:45 +0200 Subject: [ticket/11062] If user's language is english there is no further work needed PHPBB3-11062 --- phpBB/install/install_update.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB') diff --git a/phpBB/install/install_update.php b/phpBB/install/install_update.php index 0afd6b07bb..9823703f0a 100644 --- a/phpBB/install/install_update.php +++ b/phpBB/install/install_update.php @@ -239,7 +239,7 @@ class install_update extends module // this is the user's language.. just merge it $user->lang = array_merge($user->lang, $lang); } - if (in_array('language/en/install.' . $phpEx, $this->update_info['files'])) + if ($language != 'en' && in_array('language/en/install.' . $phpEx, $this->update_info['files'])) { $lang = array(); include($this->new_location . 'language/en/install.' . $phpEx); -- cgit v1.2.1 From c5de4dd51dfba4737e4b46af05546f3c6a4f8da6 Mon Sep 17 00:00:00 2001 From: MichaelC Date: Thu, 25 Jul 2013 13:06:11 +0100 Subject: [ticket/11740] Update FAQ to include Ideas Centre PHPBB3-11740 --- phpBB/language/en/help_faq.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB') diff --git a/phpBB/language/en/help_faq.php b/phpBB/language/en/help_faq.php index dab66779c3..b68336e0f7 100644 --- a/phpBB/language/en/help_faq.php +++ b/phpBB/language/en/help_faq.php @@ -333,7 +333,7 @@ $help = array( ), array( 0 => 'Why isn’t X feature available?', - 1 => 'This software was written by and licensed through phpBB Group. If you believe a feature needs to be added, or you want to report a bug, please visit the phpBB Area51 website, where you will find resources to do so.' + 1 => 'This software was written by and licensed through phpBB Group. If you believe a feature needs to be added please visit the phpBB Ideas Centre, where you can upvote existing ideas or suggest new features.' ), array( 0 => 'Who do I contact about abusive and/or legal matters related to this board?', -- cgit v1.2.1 From 866e475f9644dd3575ed62bfb0e7dde0338fd5cc Mon Sep 17 00:00:00 2001 From: Oliver Schramm Date: Thu, 25 Jul 2013 15:47:55 +0200 Subject: [ticket/10037] Apply attached patch with a few changes PHPBB3-10037 --- phpBB/includes/ucp/ucp_profile.php | 3 ++ .../styles/prosilver/template/posting_smilies.html | 4 +-- .../subsilver2/template/posting_smilies.html | 4 +-- .../subsilver2/template/ucp_profile_signature.html | 35 +++++++++++++++++++++- 4 files changed, 41 insertions(+), 5 deletions(-) (limited to 'phpBB') diff --git a/phpBB/includes/ucp/ucp_profile.php b/phpBB/includes/ucp/ucp_profile.php index d35d13b6c1..847311058b 100644 --- a/phpBB/includes/ucp/ucp_profile.php +++ b/phpBB/includes/ucp/ucp_profile.php @@ -548,6 +548,9 @@ class ucp_profile // Build custom bbcodes array display_custom_bbcodes(); + // Generate smiley listing + generate_smilies('inline', 0); + break; case 'avatar': diff --git a/phpBB/styles/prosilver/template/posting_smilies.html b/phpBB/styles/prosilver/template/posting_smilies.html index 86ac24aa53..d45b185ed3 100644 --- a/phpBB/styles/prosilver/template/posting_smilies.html +++ b/phpBB/styles/prosilver/template/posting_smilies.html @@ -2,8 +2,8 @@ diff --git a/phpBB/styles/subsilver2/template/posting_smilies.html b/phpBB/styles/subsilver2/template/posting_smilies.html index fcab578bd9..d2224887bc 100644 --- a/phpBB/styles/subsilver2/template/posting_smilies.html +++ b/phpBB/styles/subsilver2/template/posting_smilies.html @@ -2,8 +2,8 @@ diff --git a/phpBB/styles/subsilver2/template/ucp_profile_signature.html b/phpBB/styles/subsilver2/template/ucp_profile_signature.html index a33726e166..8588a99438 100644 --- a/phpBB/styles/subsilver2/template/ucp_profile_signature.html +++ b/phpBB/styles/subsilver2/template/ucp_profile_signature.html @@ -5,9 +5,11 @@
{L_TITLE}
{L_SIGNATURE_EXPLAIN}
+ + + + +
+ {L_SIGNATURE_EXPLAIN} + + + + + + + + + + + + + +
{L_SMILIES}
+ + {smiley.SMILEY_CODE} + +
{L_MORE_SMILIES}
+ +
+ @@ -47,6 +75,11 @@
+ +
+ -- cgit v1.2.1 From 57bc3c7d3aaa66fc66798bc1e60b88ae17b110a9 Mon Sep 17 00:00:00 2001 From: Nathan Guse Date: Thu, 25 Jul 2013 09:32:28 -0500 Subject: [ticket/11628] phpbb_template, not phpbb_template_interface PHPBB3-11628 --- phpBB/phpbb/controller/resolver.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB') diff --git a/phpBB/phpbb/controller/resolver.php b/phpBB/phpbb/controller/resolver.php index 850df84a0f..d772507261 100644 --- a/phpBB/phpbb/controller/resolver.php +++ b/phpBB/phpbb/controller/resolver.php @@ -48,7 +48,7 @@ class phpbb_controller_resolver implements ControllerResolverInterface * * @param phpbb_user $user User Object * @param ContainerInterface $container ContainerInterface object - * @param phpbb_template_interface $template + * @param phpbb_template $template */ public function __construct(phpbb_user $user, ContainerInterface $container, phpbb_template $template = null) { -- cgit v1.2.1 From 9e68404de5277de23baa153a29f4a3825e233e1e Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Sat, 27 Jul 2013 10:44:39 -0700 Subject: [ticket/11749] PHP Events for search.php PHPBB3-11749 --- phpBB/search.php | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) (limited to 'phpBB') diff --git a/phpBB/search.php b/phpBB/search.php index 8bcbfc498b..d0484bf598 100644 --- a/phpBB/search.php +++ b/phpBB/search.php @@ -687,6 +687,18 @@ if ($keywords || $author || $author_id || $search_id || $submit) $tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array(); } + /** + * Event to modify the SQL query before the topic data is retrieved + * + * @event core.search_get_topic_data + * @var string sql_select The SQL SELECT string used by search to get topic data + * @var string sql_from The SQL FROM string used by search to get topic data + * @var string sql_where The SQL WHERE string used by search to get topic data + * @since 3.1-A1 + */ + $vars = array('sql_select', 'sql_from', 'sql_where'); + extract($phpbb_dispatcher->trigger_event('core.search_get_topic_data', compact($vars))); + $sql = "SELECT $sql_select FROM $sql_from WHERE $sql_where"; @@ -989,6 +1001,17 @@ if ($keywords || $author || $author_id || $search_id || $submit) ); } + /** + * Modify the topic data before it is assigned to the template + * + * @event core.search_modify_tpl_ary + * @var array row Array with topic data + * @var array tpl_ary Template block array with topic data + * @since 3.1-A1 + */ + $vars = array('row', 'tpl_ary'); + extract($phpbb_dispatcher->trigger_event('core.search_modify_tpl_ary', compact($vars))); + $template->assign_block_vars('searchresults', array_merge($tpl_ary, array( 'FORUM_ID' => $forum_id, 'TOPIC_ID' => $result_topic_id, -- cgit v1.2.1 From 9ffb150d47d3db42c1816ffa662e73046a1b5d03 Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Sat, 27 Jul 2013 10:45:40 -0700 Subject: [ticket/11749] PHP Events for viewforum.php PHPBB3-11749 --- phpBB/viewforum.php | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) (limited to 'phpBB') diff --git a/phpBB/viewforum.php b/phpBB/viewforum.php index 5a59e021b3..1fa2030671 100644 --- a/phpBB/viewforum.php +++ b/phpBB/viewforum.php @@ -372,6 +372,16 @@ $sql_array = array( 'LEFT_JOIN' => array(), ); +/** +* Event to modify the SQL query before the topic data is retrieved +* +* @event core.viewforum_get_topic_data +* @var array sql_array The SQL array to get the data of all topics +* @since 3.1-A1 +*/ +$vars = array('sql_array'); +extract($phpbb_dispatcher->trigger_event('core.viewforum_get_topic_data', compact($vars))); + $sql_approved = ' AND ' . $phpbb_content_visibility->get_visibility_sql('topic', $forum_id, 't.'); if ($user->data['is_registered']) @@ -554,6 +564,17 @@ if (sizeof($shadow_topic_list)) $sql = 'SELECT * FROM ' . TOPICS_TABLE . ' WHERE ' . $db->sql_in_set('topic_id', array_keys($shadow_topic_list)); + + /** + * Event to modify the SQL query before the shadowtopic data is retrieved + * + * @event core.viewforum_get_shadowtopic_data + * @var string sql The SQL string to get the data of any shadowtopics + * @since 3.1-A1 + */ + $vars = array('sql'); + extract($phpbb_dispatcher->trigger_event('core.viewforum_get_shadowtopic_data', compact($vars))); + $result = $db->sql_query($sql); while ($row = $db->sql_fetchrow($result)) -- cgit v1.2.1 From 8e636e4572ef11af61f2b2c6dfb56d7a9530c8de Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Sat, 27 Jul 2013 10:48:40 -0700 Subject: [ticket/11749] Template events for topic_list_row_pre/append PHPBB3-11749 --- phpBB/docs/events.md | 18 ++++++++++++++++++ phpBB/styles/prosilver/template/search_results.html | 2 ++ phpBB/styles/prosilver/template/viewforum_body.html | 2 ++ phpBB/styles/subsilver2/template/search_results.html | 2 ++ phpBB/styles/subsilver2/template/viewforum_body.html | 4 ++++ 5 files changed, 28 insertions(+) (limited to 'phpBB') diff --git a/phpBB/docs/events.md b/phpBB/docs/events.md index 3723bf7b3f..855f238653 100644 --- a/phpBB/docs/events.md +++ b/phpBB/docs/events.md @@ -114,6 +114,24 @@ simple_footer_after * Location: styles/prosilver/template/simple_footer.html * Purpose: Add content directly prior to the `` tag of the simple footer +topiclist_row_prepend +=== +* Locations: + + styles/prosilver/template/search_results.html + + styles/prosilver/template/viewforum_body.html + + styles/subsilver2/template/search_results.html + + styles/subsilver2/template/viewforum_body.html +* Purpose: Add content into topic rows (inside the elements containing topic titles) + +topiclist_row_append +=== +* Locations: + + styles/prosilver/template/search_results.html + + styles/prosilver/template/viewforum_body.html + + styles/subsilver2/template/search_results.html + + styles/subsilver2/template/viewforum_body.html +* Purpose: Add content into topic rows (inside the elements containing topic titles) + ucp_pm_viewmessage_print_head_append === * Location: styles/prosilver/template/ucp_pm_viewmessage_print.html diff --git a/phpBB/styles/prosilver/template/search_results.html b/phpBB/styles/prosilver/template/search_results.html index f0424c45db..54e8867526 100644 --- a/phpBB/styles/prosilver/template/search_results.html +++ b/phpBB/styles/prosilver/template/search_results.html @@ -63,6 +63,7 @@
style="background-image: url({T_ICONS_PATH}{searchresults.TOPIC_ICON_IMG}); background-repeat: no-repeat;" title="{searchresults.TOPIC_FOLDER_IMG_ALT}">
+ {NEWEST_POST_IMG} {searchresults.TOPIC_TITLE} {searchresults.ATTACH_ICON_IMG} {searchresults.UNAPPROVED_IMG} @@ -83,6 +84,7 @@
{L_POST_BY_AUTHOR} {searchresults.TOPIC_AUTHOR_FULL} » {searchresults.FIRST_POST_TIME} » {L_IN} {searchresults.FORUM_TITLE} +
diff --git a/phpBB/styles/prosilver/template/viewforum_body.html b/phpBB/styles/prosilver/template/viewforum_body.html index 69b0608a64..ecd993d7fb 100644 --- a/phpBB/styles/prosilver/template/viewforum_body.html +++ b/phpBB/styles/prosilver/template/viewforum_body.html @@ -144,6 +144,7 @@
style="background-image: url({T_ICONS_PATH}{topicrow.TOPIC_ICON_IMG}); background-repeat: no-repeat;" title="{topicrow.TOPIC_FOLDER_IMG_ALT}">
+ {NEWEST_POST_IMG} {topicrow.TOPIC_TITLE} {topicrow.UNAPPROVED_IMG} {DELETED_IMG} @@ -164,6 +165,7 @@ {topicrow.ATTACH_ICON_IMG} {L_POST_BY_AUTHOR} {topicrow.TOPIC_AUTHOR_FULL} » {topicrow.FIRST_POST_TIME} » {L_IN} {topicrow.FORUM_NAME} +
{topicrow.REPLIES} {L_REPLIES}
diff --git a/phpBB/styles/subsilver2/template/search_results.html b/phpBB/styles/subsilver2/template/search_results.html index d98079de20..19ba0b196a 100644 --- a/phpBB/styles/subsilver2/template/search_results.html +++ b/phpBB/styles/subsilver2/template/search_results.html @@ -34,6 +34,7 @@ + {NEWEST_POST_IMG} {searchresults.ATTACH_ICON_IMG} {searchresults.TOPIC_TITLE} @@ -58,6 +59,7 @@ ]

{L_IN} {searchresults.FORUM_TITLE}

+

{searchresults.TOPIC_AUTHOR_FULL}

{searchresults.TOPIC_REPLIES}

diff --git a/phpBB/styles/subsilver2/template/viewforum_body.html b/phpBB/styles/subsilver2/template/viewforum_body.html index d07e9a1372..dfbe0a605b 100644 --- a/phpBB/styles/subsilver2/template/viewforum_body.html +++ b/phpBB/styles/subsilver2/template/viewforum_body.html @@ -40,6 +40,7 @@ + {NEWEST_POST_IMG} {topicrow.ATTACH_ICON_IMG} {topicrow.TOPIC_TYPE} {topicrow.TOPIC_TITLE} @@ -63,6 +64,7 @@ ]

+

{topicrow.TOPIC_AUTHOR_FULL}

{topicrow.REPLIES}

@@ -203,6 +205,7 @@ + {NEWEST_POST_IMG} {topicrow.ATTACH_ICON_IMG} {topicrow.TOPIC_TYPE} {topicrow.TOPIC_TITLE} @@ -227,6 +230,7 @@ ]

{L_IN} {topicrow.FORUM_NAME}

+

{topicrow.TOPIC_AUTHOR_FULL}

{topicrow.REPLIES}

-- cgit v1.2.1 From d8584877a19ece5b5d6cd1d0ec752905ef8501e7 Mon Sep 17 00:00:00 2001 From: Oliver Schramm Date: Sat, 27 Jul 2013 22:37:44 +0200 Subject: [ticket/10917] Revert use of phpbb wrapper PHPBB3-10917 --- phpBB/install/install_update.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB') diff --git a/phpBB/install/install_update.php b/phpBB/install/install_update.php index 9ac17e7579..e8fcabfc2d 100644 --- a/phpBB/install/install_update.php +++ b/phpBB/install/install_update.php @@ -204,7 +204,7 @@ class install_update extends module } // Check if the update files stored are for the latest version... - if (phpbb_version_compare($this->latest_version, $this->update_info['version']['to'], '>')) + if (version_compare(strtolower($this->latest_version), strtolower($this->update_info['version']['to']), '>')) { $this->unequal_version = true; -- cgit v1.2.1 From d5c56c5d503ea4b12852866e2d3b956e92a92aea Mon Sep 17 00:00:00 2001 From: Nathan Guse Date: Sat, 27 Jul 2013 20:02:03 -0500 Subject: [ticket/11724] Support "ELSE IF" and "ELSEIF" in the same way PHPBB3-11724 --- phpBB/phpbb/template/twig/lexer.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'phpBB') diff --git a/phpBB/phpbb/template/twig/lexer.php b/phpBB/phpbb/template/twig/lexer.php index 4f88147542..1a640e559e 100644 --- a/phpBB/phpbb/template/twig/lexer.php +++ b/phpBB/phpbb/template/twig/lexer.php @@ -236,7 +236,8 @@ class phpbb_template_twig_lexer extends Twig_Lexer // Replace our "div by" with Twig's divisibleby (Twig does not like test names with spaces) $code = preg_replace('# div by ([0-9]+)#', ' divisibleby($1)', $code); - return preg_replace_callback('##', $callback, $code); + // (ELSE)?\s?IF; match IF|ELSEIF|ELSE IF; replace ELSE IF with ELSEIF + return preg_replace_callback('##', $callback, $code); } /** -- cgit v1.2.1 From dd875f13e875a726c9ea4654a90a74878658e423 Mon Sep 17 00:00:00 2001 From: Oliver Schramm Date: Sun, 28 Jul 2013 02:35:01 +0200 Subject: [ticket/10917] Variable used only once so delete it The variable $this->unequal_version was only used once and only to display the version the package updates to. To display the version it updates to makes no sense when the update files just aren't meant to update from the current version. (It's already shown in an error message) So I deleted the variable from there. Furthermore the use of version_compare makes the variable useless in that context which is why I deleted the variable from the whole file and replaced it in the relevant if statement with the old comparison. PHPBB3-10917 --- phpBB/install/install_update.php | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) (limited to 'phpBB') diff --git a/phpBB/install/install_update.php b/phpBB/install/install_update.php index e8fcabfc2d..2f3ee1c55a 100644 --- a/phpBB/install/install_update.php +++ b/phpBB/install/install_update.php @@ -58,7 +58,6 @@ class install_update extends module var $new_location; var $latest_version; var $current_version; - var $unequal_version; var $update_to_version; @@ -76,7 +75,6 @@ class install_update extends module $this->tpl_name = 'install_update'; $this->page_title = 'UPDATE_INSTALLATION'; - $this->unequal_version = false; $this->old_location = $phpbb_root_path . 'install/update/old/'; $this->new_location = $phpbb_root_path . 'install/update/new/'; @@ -195,8 +193,6 @@ class install_update extends module // Check if the update files are actually meant to update from the current version if ($this->current_version != $this->update_info['version']['from']) { - $this->unequal_version = true; - $template->assign_vars(array( 'S_ERROR' => true, 'ERROR_MSG' => sprintf($user->lang['INCOMPATIBLE_UPDATE_FILES'], $this->current_version, $this->update_info['version']['from'], $this->update_info['version']['to']), @@ -206,8 +202,6 @@ class install_update extends module // Check if the update files stored are for the latest version... if (version_compare(strtolower($this->latest_version), strtolower($this->update_info['version']['to']), '>')) { - $this->unequal_version = true; - $template->assign_vars(array( 'S_WARNING' => true, 'WARNING_MSG' => sprintf($user->lang['OLD_UPDATE_FILES'], $this->update_info['version']['from'], $this->update_info['version']['to'], $this->latest_version)) @@ -294,7 +288,7 @@ class install_update extends module ); // Print out version the update package updates to - if ($this->unequal_version) + if ($this->latest_version != $this->update_info['version']['to']) { $template->assign_var('PACKAGE_VERSION', $this->update_info['version']['to']); } -- cgit v1.2.1 From 0215e0bd95104c628cf084e1be0df72b2752346c Mon Sep 17 00:00:00 2001 From: Nathan Guse Date: Sun, 28 Jul 2013 21:31:12 -0500 Subject: [ticket/11724] Replace spaces with tabs PHPBB3-11724 --- phpBB/phpbb/template/twig/lexer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB') diff --git a/phpBB/phpbb/template/twig/lexer.php b/phpBB/phpbb/template/twig/lexer.php index 1a640e559e..7cb84167bf 100644 --- a/phpBB/phpbb/template/twig/lexer.php +++ b/phpBB/phpbb/template/twig/lexer.php @@ -237,7 +237,7 @@ class phpbb_template_twig_lexer extends Twig_Lexer $code = preg_replace('# div by ([0-9]+)#', ' divisibleby($1)', $code); // (ELSE)?\s?IF; match IF|ELSEIF|ELSE IF; replace ELSE IF with ELSEIF - return preg_replace_callback('##', $callback, $code); + return preg_replace_callback('##', $callback, $code); } /** -- cgit v1.2.1 From 9902f1c75178aa72d3b89f51033fcda552141b40 Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Mon, 29 Jul 2013 00:05:31 -0700 Subject: [ticket/11749] Move event after all template data has been defined PHPBB3-11749 --- phpBB/search.php | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) (limited to 'phpBB') diff --git a/phpBB/search.php b/phpBB/search.php index d0484bf598..40c0b9a8ce 100644 --- a/phpBB/search.php +++ b/phpBB/search.php @@ -1001,18 +1001,7 @@ if ($keywords || $author || $author_id || $search_id || $submit) ); } - /** - * Modify the topic data before it is assigned to the template - * - * @event core.search_modify_tpl_ary - * @var array row Array with topic data - * @var array tpl_ary Template block array with topic data - * @since 3.1-A1 - */ - $vars = array('row', 'tpl_ary'); - extract($phpbb_dispatcher->trigger_event('core.search_modify_tpl_ary', compact($vars))); - - $template->assign_block_vars('searchresults', array_merge($tpl_ary, array( + $tpl_ary = array_merge($tpl_ary, array( 'FORUM_ID' => $forum_id, 'TOPIC_ID' => $result_topic_id, 'POST_ID' => ($show_results == 'posts') ? $row['post_id'] : false, @@ -1024,9 +1013,22 @@ if ($keywords || $author || $author_id || $search_id || $submit) 'U_VIEW_TOPIC' => $view_topic_url, 'U_VIEW_FORUM' => append_sid("{$phpbb_root_path}viewforum.$phpEx", 'f=' . $forum_id), - 'U_VIEW_POST' => (!empty($row['post_id'])) ? append_sid("{$phpbb_root_path}viewtopic.$phpEx", "f=$forum_id&t=" . $row['topic_id'] . '&p=' . $row['post_id'] . (($u_hilit) ? '&hilit=' . $u_hilit : '')) . '#p' . $row['post_id'] : '') + 'U_VIEW_POST' => (!empty($row['post_id'])) ? append_sid("{$phpbb_root_path}viewtopic.$phpEx", "f=$forum_id&t=" . $row['topic_id'] . '&p=' . $row['post_id'] . (($u_hilit) ? '&hilit=' . $u_hilit : '')) . '#p' . $row['post_id'] : '', )); + /** + * Modify the topic data before it is assigned to the template + * + * @event core.search_modify_tpl_ary + * @var array row Array with topic data + * @var array tpl_ary Template block array with topic data + * @since 3.1-A1 + */ + $vars = array('row', 'tpl_ary'); + extract($phpbb_dispatcher->trigger_event('core.search_modify_tpl_ary', compact($vars))); + + $template->assign_block_vars('searchresults', $tpl_ary); + if ($show_results == 'topics') { phpbb_generate_template_pagination($template, $view_topic_url, 'searchresults.pagination', 'start', $replies + 1, $config['posts_per_page'], 1, true, true); -- cgit v1.2.1 From b8fef3b33a5c04c6637667b0a9a9f3b24ba2c594 Mon Sep 17 00:00:00 2001 From: Bruno Ais Date: Mon, 29 Jul 2013 16:55:58 +0100 Subject: [ticket/11640] removed the space that I wonder what it was doing there. sub-task of ticket PHPBB3-11635: find and fix all bypasses of generate_text_for_* PHPBB3-11640 --- phpBB/includes/functions_privmsgs.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB') diff --git a/phpBB/includes/functions_privmsgs.php b/phpBB/includes/functions_privmsgs.php index 15907feedd..5fc6de8e02 100644 --- a/phpBB/includes/functions_privmsgs.php +++ b/phpBB/includes/functions_privmsgs.php @@ -2022,7 +2022,7 @@ function message_history($msg_id, $user_id, $message_row, $folder, $in_post_mode $parse_flags = ($row['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0); $parse_flags |= ($row['enable_smilies'] ? OPTION_FLAG_SMILIES : 0); - $message = generate_text_for_display($message, $row['bbcode_uid'], $row['bbcode_bitfield'], $parse_flags , false); + $message = generate_text_for_display($message, $row['bbcode_uid'], $row['bbcode_bitfield'], $parse_flags, false); $subject = censor_text($subject); -- cgit v1.2.1 From ccc5c5f6b8c197c0f96349e139472ddbe17f19ce Mon Sep 17 00:00:00 2001 From: Bruno Ais Date: Mon, 29 Jul 2013 17:00:51 +0100 Subject: [ticket/11638] Changed the layout to match the other similar commits sub-task of ticket PHPBB3-11635: find and fix all bypasses of generate_text_for_* PHPBB3-11638 --- phpBB/viewtopic.php | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) (limited to 'phpBB') diff --git a/phpBB/viewtopic.php b/phpBB/viewtopic.php index 64006fbe61..a24c40f697 100644 --- a/phpBB/viewtopic.php +++ b/phpBB/viewtopic.php @@ -828,19 +828,14 @@ if (!empty($topic_data['poll_start'])) $poll_total += $poll_option['poll_option_total']; } - $parse_bbcode_flags = OPTION_FLAG_SMILIES; - - if (empty($poll_info[0]['bbcode_bitfield'])) - { - $parse_bbcode_flags |= OPTION_FLAG_BBCODE; - } + $parse_flags = ($row['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES; for ($i = 0, $size = sizeof($poll_info); $i < $size; $i++) { - $poll_info[$i]['poll_option_text'] = generate_text_for_display($poll_info[$i]['poll_option_text'], $poll_info[$i]['bbcode_uid'], $poll_option['bbcode_bitfield'], $parse_bbcode_flags, true); + $poll_info[$i]['poll_option_text'] = generate_text_for_display($poll_info[$i]['poll_option_text'], $poll_info[$i]['bbcode_uid'], $poll_option['bbcode_bitfield'], $parse_flags, true); } - $topic_data['poll_title'] = generate_text_for_display($topic_data['poll_title'], $poll_info[0]['bbcode_uid'], $poll_info[0]['bbcode_bitfield'], $parse_bbcode_flags, true); + $topic_data['poll_title'] = generate_text_for_display($topic_data['poll_title'], $poll_info[0]['bbcode_uid'], $poll_info[0]['bbcode_bitfield'], $parse_flags, true); foreach ($poll_info as $poll_option) { -- cgit v1.2.1 From 5bb08a1ab973ee13237876d11b001c4ed6658892 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Mon, 29 Jul 2013 21:30:01 +0200 Subject: [ticket/11574] Change order of files and database update PHPBB3-11574 --- phpBB/adm/style/install_update.html | 46 ++++++++++++++++++------------------- phpBB/install/database_update.php | 3 ++- phpBB/install/index.php | 2 +- phpBB/install/install_update.php | 33 +++++++++++++++++++------- phpBB/language/en/install.php | 6 +++-- 5 files changed, 55 insertions(+), 35 deletions(-) (limited to 'phpBB') diff --git a/phpBB/adm/style/install_update.html b/phpBB/adm/style/install_update.html index b5fa46dbf6..bd46f7877e 100644 --- a/phpBB/adm/style/install_update.html +++ b/phpBB/adm/style/install_update.html @@ -109,27 +109,14 @@ - - -
- -
-

{L_UPDATE_DATABASE_EXPLAIN}

- -
- -
- - -
+ -
-

{L_CHECK_FILES_UP_TO_DATE}

- -
+
+

{L_CHECK_FILES_EXPLAIN}

+ +
-
- + @@ -155,6 +142,11 @@ +
+

{L_UPDATE_DB_SUCCESS}

+

{L_EVERYTHING_UP_TO_DATE}

+
+

{L_UPDATE_DB_SUCCESS}



@@ -174,10 +166,18 @@ -
-

{L_UPDATE_SUCCESS}

-

{L_ALL_FILES_UP_TO_DATE}

-
+

{L_UPDATE_FILE_SUCCESS}

+

{L_ALL_FILES_UP_TO_DATE}

+ +

{L_UPDATE_DATABASE_EXPLAIN}

+ +
+ +
+ +
+ +

{L_COLLECTED_INFORMATION}

diff --git a/phpBB/install/database_update.php b/phpBB/install/database_update.php index 44cbc74d29..f69f0f6986 100644 --- a/phpBB/install/database_update.php +++ b/phpBB/install/database_update.php @@ -110,6 +110,7 @@ phpbb_require_updated('includes/functions_container.' . $phpEx); require($phpbb_root_path . 'config.' . $phpEx); phpbb_require_updated('includes/constants.' . $phpEx); +phpbb_include_updated('includes/utf/utf_normalizer.' . $phpEx); phpbb_require_updated('includes/utf/utf_tools.' . $phpEx); // Set PHP error handler to ours @@ -305,7 +306,7 @@ echo $user->lang['DATABASE_UPDATE_COMPLETE'] . '
'; if ($request->variable('type', 0)) { echo $user->lang['INLINE_UPDATE_SUCCESSFUL'] . '

'; - echo '' . $user->lang['CONTINUE_UPDATE_NOW'] . ''; + echo '' . $user->lang['CONTINUE_UPDATE_NOW'] . ''; } else { diff --git a/phpBB/install/index.php b/phpBB/install/index.php index fe61c53558..bd39e231f4 100644 --- a/phpBB/install/index.php +++ b/phpBB/install/index.php @@ -250,7 +250,7 @@ $template = new phpbb_template_twig($phpbb_root_path, $phpEx, $config, $user, ne $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/'); -$paths = array($phpbb_admin_path . 'style', $phpbb_root_path . 'install/update/new/adm/style'); +$paths = array($phpbb_root_path . 'install/update/new/adm/style', $phpbb_admin_path . 'style'); $paths = array_filter($paths, 'is_dir'); $phpbb_style->set_custom_style('admin', $paths, array(), ''); diff --git a/phpBB/install/install_update.php b/phpBB/install/install_update.php index 478cc9f76f..a8abfc7cfc 100644 --- a/phpBB/install/install_update.php +++ b/phpBB/install/install_update.php @@ -39,7 +39,7 @@ if (!empty($setmodules)) 'module_filename' => substr(basename(__FILE__), 0, -strlen($phpEx)-1), 'module_order' => 30, 'module_subs' => '', - 'module_stages' => array('INTRO', 'VERSION_CHECK', 'UPDATE_DB', 'FILE_CHECK', 'UPDATE_FILES'), + 'module_stages' => array('INTRO', 'VERSION_CHECK', 'FILE_CHECK', 'UPDATE_FILES', 'UPDATE_DB'), 'module_reqs' => '' ); } @@ -74,6 +74,11 @@ class install_update extends module global $phpbb_style, $template, $phpEx, $phpbb_root_path, $user, $db, $config, $cache, $auth, $language; global $request, $phpbb_admin_path, $phpbb_adm_relative_path, $phpbb_container; + // We must enable super globals, otherwise creating a new instance of the request class, + // using the new container with a dbal connection will fail with the following PHP Notice: + // Object of class phpbb_request_deactivated_super_global could not be converted to int + $request->enable_super_globals(); + // Create a normal container now $phpbb_container = phpbb_create_update_container($phpbb_root_path, $phpEx, $phpbb_root_path . 'install/update/new/config'); @@ -138,7 +143,9 @@ class install_update extends module } // Set custom template again. ;) - $phpbb_style->set_custom_style('admin', $phpbb_admin_path . 'style', array(), ''); + $paths = array($phpbb_root_path . 'install/update/new/adm/style', $phpbb_admin_path . 'style'); + $paths = array_filter($paths, 'is_dir'); + $phpbb_style->set_custom_style('admin', $paths, array(), ''); $template->assign_vars(array( 'S_USER_LANG' => $user->lang['USER_LANG'], @@ -267,15 +274,14 @@ class install_update extends module $this->page_title = 'STAGE_VERSION_CHECK'; $template->assign_vars(array( - 'S_UP_TO_DATE' => $up_to_date, 'S_VERSION_CHECK' => true, - 'U_ACTION' => append_sid($this->p_master->module_url, "language=$language&mode=$mode&sub=file_check"), - 'U_DB_UPDATE_ACTION' => append_sid($this->p_master->module_url, "language=$language&mode=$mode&sub=update_db"), + 'U_ACTION' => append_sid($this->p_master->module_url, "language=$language&mode=$mode&sub=file_check"), + 'S_UP_TO_DATE' => $up_to_date, 'LATEST_VERSION' => $this->latest_version, - 'CURRENT_VERSION' => $this->current_version) - ); + 'CURRENT_VERSION' => $this->current_version, + )); // Print out version the update package updates to if ($this->unequal_version) @@ -303,6 +309,7 @@ class install_update extends module 'U_DB_UPDATE' => append_sid($phpbb_root_path . 'install/database_update.' . $phpEx, 'type=1&language=' . $user->data['user_lang']), 'U_DB_UPDATE_ACTION' => append_sid($this->p_master->module_url, "language=$language&mode=$mode&sub=update_db"), 'U_ACTION' => append_sid($this->p_master->module_url, "language=$language&mode=$mode&sub=file_check"), + 'L_EVERYTHING_UP_TO_DATE' => $user->lang('EVERYTHING_UP_TO_DATE', append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=login'), append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=login&redirect=' . $phpbb_adm_relative_path . 'index.php%3Fi=send_statistics%26mode=send_statistics')), )); break; @@ -470,13 +477,23 @@ class install_update extends module $template->assign_vars(array( 'S_FILE_CHECK' => true, 'S_ALL_UP_TO_DATE' => $all_up_to_date, - 'L_ALL_FILES_UP_TO_DATE' => $user->lang('ALL_FILES_UP_TO_DATE', append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=login'), append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=login&redirect=' . $phpbb_adm_relative_path . 'index.php%3Fi=send_statistics%26mode=send_statistics')), 'S_VERSION_UP_TO_DATE' => $up_to_date, + 'S_UP_TO_DATE' => $up_to_date, 'U_ACTION' => append_sid($this->p_master->module_url, "language=$language&mode=$mode&sub=file_check"), 'U_UPDATE_ACTION' => append_sid($this->p_master->module_url, "language=$language&mode=$mode&sub=update_files"), 'U_DB_UPDATE_ACTION' => append_sid($this->p_master->module_url, "language=$language&mode=$mode&sub=update_db"), )); + // Since some people try to update to RC releases, but phpBB.com tells them the last version is the version they currently run + // we are faced with the updater thinking the database schema is up-to-date; which it is, but should be updated none-the-less + // We now try to cope with this by triggering the update process + if (version_compare(str_replace('rc', 'RC', strtolower($this->current_version)), str_replace('rc', 'RC', strtolower($this->update_info['version']['to'])), '<')) + { + $template->assign_vars(array( + 'S_UP_TO_DATE' => false, + )); + } + if ($all_up_to_date) { global $phpbb_container; diff --git a/phpBB/language/en/install.php b/phpBB/language/en/install.php index be45047861..f994f339a9 100644 --- a/phpBB/language/en/install.php +++ b/phpBB/language/en/install.php @@ -374,7 +374,7 @@ $lang = array_merge($lang, array( // Updater $lang = array_merge($lang, array( - 'ALL_FILES_UP_TO_DATE' => 'All files are up to date with the latest phpBB version. You should now login to your board and check if everything is working fine. Do not forget to delete, rename or move your install directory! Please send us updated information about your server and board configurations from the Send statistics module in your ACP.', + 'ALL_FILES_UP_TO_DATE' => 'All files are up to date with the latest phpBB version.', 'ARCHIVE_FILE' => 'Source file within archive', 'BACK' => 'Back', @@ -419,8 +419,9 @@ $lang = array_merge($lang, array( 'DOWNLOAD_UPDATE_METHOD' => 'Download modified files archive', 'DOWNLOAD_UPDATE_METHOD_EXPLAIN' => 'Once downloaded you should unpack the archive. You will find the modified files you need to upload to your phpBB root directory within it. Please upload the files to their respective locations then. After you have uploaded all files, please check the files again with the other button below.', - 'ERROR' => 'Error', 'EDIT_USERNAME' => 'Edit username', + 'ERROR' => 'Error', + 'EVERYTHING_UP_TO_DATE' => 'Everything is up to date with the latest phpBB version. You should now login to your board and check if everything is working fine. Do not forget to delete, rename or move your install directory! Please send us updated information about your server and board configurations from the Send statistics module in your ACP.', 'FILE_ALREADY_UP_TO_DATE' => 'File is already up to date.', 'FILE_DIFF_NOT_ALLOWED' => 'File not allowed to be diffed.', @@ -570,6 +571,7 @@ $lang = array_merge($lang, array( 'UPLOAD_METHOD' => 'Upload method', 'UPDATE_DB_SUCCESS' => 'Database update was successful.', + 'UPDATE_FILE_SUCCESS' => 'File update was successful.', 'USER_ACTIVE' => 'Active user', 'USER_INACTIVE' => 'Inactive user', -- cgit v1.2.1 From 5f3f41d6d6fb5c997ab7b70482fef542a5534b6a Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Mon, 29 Jul 2013 23:47:31 +0200 Subject: [ticket/11574] Remove old "continue step"-message PHPBB3-11574 --- phpBB/adm/style/install_update.html | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) (limited to 'phpBB') diff --git a/phpBB/adm/style/install_update.html b/phpBB/adm/style/install_update.html index bd46f7877e..57e2c8ffea 100644 --- a/phpBB/adm/style/install_update.html +++ b/phpBB/adm/style/install_update.html @@ -143,23 +143,10 @@
-

{L_UPDATE_DB_SUCCESS}

+

{L_UPDATE_SUCCESS}

{L_EVERYTHING_UP_TO_DATE}

-

{L_UPDATE_DB_SUCCESS}

- -

- -
- -
-

{L_CHECK_FILES_EXPLAIN}

- -
- -
- -- cgit v1.2.1 From 18164e63e2897a755a214b8fcb4b6d84897888e6 Mon Sep 17 00:00:00 2001 From: Andreas Fischer Date: Tue, 30 Jul 2013 01:06:10 +0200 Subject: [ticket/11752] HTTP -> HTTPs in email/installed.txt PHPBB3-11752 --- phpBB/language/en/email/installed.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB') diff --git a/phpBB/language/en/email/installed.txt b/phpBB/language/en/email/installed.txt index 2aa03a7f33..60e52e37c4 100644 --- a/phpBB/language/en/email/installed.txt +++ b/phpBB/language/en/email/installed.txt @@ -12,7 +12,7 @@ Username: {USERNAME} Board URL: {U_BOARD} ---------------------------- -Useful information regarding the phpBB software can be found in the docs folder of your installation and on phpBB.com's support page - http://www.phpbb.com/support/ +Useful information regarding the phpBB software can be found in the docs folder of your installation and on phpBB.com's support page - https://www.phpbb.com/support/ In order to keep your board safe and secure, we highly recommended keeping current with software releases. For your convenience, a mailing list is available at the page referenced above. -- cgit v1.2.1 From 0ff2e93c1937f96f8fcd733a152a0c2f263706b1 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Tue, 30 Jul 2013 01:18:32 +0200 Subject: [ticket/11574] Do not display incompatible package note after successful update PHPBB3-11574 --- phpBB/install/install_update.php | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'phpBB') diff --git a/phpBB/install/install_update.php b/phpBB/install/install_update.php index dce0134730..4ae39f202f 100644 --- a/phpBB/install/install_update.php +++ b/phpBB/install/install_update.php @@ -313,6 +313,11 @@ class install_update extends module 'L_EVERYTHING_UP_TO_DATE' => $user->lang('EVERYTHING_UP_TO_DATE', append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=login'), append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=login&redirect=' . $phpbb_adm_relative_path . 'index.php%3Fi=send_statistics%26mode=send_statistics')), )); + // Do not display incompatible package note after successful update + if ($config['version'] == $this->update_info['version']['to']) + { + $template->assign_var('S_ERROR', false); + } break; case 'file_check': -- cgit v1.2.1 From 32499c8808bb72812f66ba00e35c5af128a4d4e2 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Tue, 30 Jul 2013 01:38:06 +0200 Subject: [ticket/11574] Remove install/udpate/new/ fallback from database_update.php Since we switched the order, everything should be in the normal root by then. PHPBB3-11574 --- phpBB/install/database_update.php | 67 +++++++-------------------------------- phpBB/language/en/install.php | 2 +- 2 files changed, 12 insertions(+), 57 deletions(-) (limited to 'phpBB') diff --git a/phpBB/install/database_update.php b/phpBB/install/database_update.php index f69f0f6986..3be5ea659c 100644 --- a/phpBB/install/database_update.php +++ b/phpBB/install/database_update.php @@ -21,46 +21,6 @@ define('IN_INSTALL', true); $phpbb_root_path = (defined('PHPBB_ROOT_PATH')) ? PHPBB_ROOT_PATH : './../'; $phpEx = substr(strrchr(__FILE__, '.'), 1); -if (!function_exists('phpbb_require_updated')) -{ - function phpbb_require_updated($path, $optional = false) - { - global $phpbb_root_path, $table_prefix; - - $new_path = $phpbb_root_path . 'install/update/new/' . $path; - $old_path = $phpbb_root_path . $path; - - if (file_exists($new_path)) - { - require($new_path); - } - else if (!$optional || file_exists($old_path)) - { - require($old_path); - } - } -} - -if (!function_exists('phpbb_include_updated')) -{ - function phpbb_include_updated($path, $optional = false) - { - global $phpbb_root_path; - - $new_path = $phpbb_root_path . 'install/update/new/' . $path; - $old_path = $phpbb_root_path . $path; - - if (file_exists($new_path)) - { - include($new_path); - } - else if (!$optional || file_exists($old_path)) - { - include($old_path); - } - } -} - function phpbb_end_update($cache, $config) { $cache->purge(); @@ -89,7 +49,7 @@ function phpbb_end_update($cache, $config) exit_handler(); } -phpbb_require_updated('includes/startup.' . $phpEx); +require($phpbb_root_path . 'includes/startup.' . $phpEx); include($phpbb_root_path . 'config.' . $phpEx); if (!defined('PHPBB_INSTALLED') || empty($dbms) || empty($acm_type)) @@ -102,33 +62,28 @@ $phpbb_adm_relative_path = (isset($phpbb_adm_relative_path)) ? $phpbb_adm_relati $phpbb_admin_path = (defined('PHPBB_ADMIN_PATH')) ? PHPBB_ADMIN_PATH : $phpbb_root_path . $phpbb_adm_relative_path; // Include files -phpbb_require_updated('phpbb/class_loader.' . $phpEx); +require($phpbb_root_path . 'phpbb/class_loader.' . $phpEx); -phpbb_require_updated('includes/functions.' . $phpEx); -phpbb_require_updated('includes/functions_content.' . $phpEx); -phpbb_require_updated('includes/functions_container.' . $phpEx); +require($phpbb_root_path . 'includes/functions.' . $phpEx); +require($phpbb_root_path . 'includes/functions_content.' . $phpEx); +require($phpbb_root_path . 'includes/functions_container.' . $phpEx); require($phpbb_root_path . 'config.' . $phpEx); -phpbb_require_updated('includes/constants.' . $phpEx); -phpbb_include_updated('includes/utf/utf_normalizer.' . $phpEx); -phpbb_require_updated('includes/utf/utf_tools.' . $phpEx); +require($phpbb_root_path . 'includes/constants.' . $phpEx); +include($phpbb_root_path . 'includes/utf/utf_normalizer.' . $phpEx); +require($phpbb_root_path . 'includes/utf/utf_tools.' . $phpEx); // Set PHP error handler to ours set_error_handler(defined('PHPBB_MSG_HANDLER') ? PHPBB_MSG_HANDLER : 'msg_handler'); // Setup class loader first -$phpbb_class_loader_new = new phpbb_class_loader('phpbb_', "{$phpbb_root_path}install/update/new/phpbb/", $phpEx); -$phpbb_class_loader_new->register(); $phpbb_class_loader = new phpbb_class_loader('phpbb_', "{$phpbb_root_path}phpbb/", $phpEx); $phpbb_class_loader->register(); // Set up container (must be done here because extensions table may not exist) -$other_config_path = $phpbb_root_path . 'install/update/new/config/'; -$config_path = file_exists($other_config_path . 'services.yml') ? $other_config_path : $phpbb_root_path . 'config/'; - $container_extensions = array( new phpbb_di_extension_config($phpbb_root_path . 'config.' . $phpEx), - new phpbb_di_extension_core($config_path), + new phpbb_di_extension_core($phpbb_root_path . 'config/'), ); $container_passes = array( new phpbb_di_pass_collection_pass(), @@ -289,8 +244,8 @@ while (!$migrator->finished()) // Are we approaching the time limit? If so we want to pause the update and continue after refreshing if ((time() - $update_start_time) >= $safe_time_limit) { - echo $user->lang['DATABASE_UPDATE_NOT_COMPLETED'] . '
'; - echo '' . $user->lang['DATABASE_UPDATE_CONTINUE'] . ''; + echo '
' . $user->lang['DATABASE_UPDATE_NOT_COMPLETED'] . '

'; + echo '' . $user->lang['DATABASE_UPDATE_CONTINUE'] . ''; phpbb_end_update($cache, $config); } diff --git a/phpBB/language/en/install.php b/phpBB/language/en/install.php index f994f339a9..03c9562983 100644 --- a/phpBB/language/en/install.php +++ b/phpBB/language/en/install.php @@ -398,7 +398,7 @@ $lang = array_merge($lang, array( 'DATABASE_TYPE' => 'Database type', 'DATABASE_UPDATE_COMPLETE' => 'Database updater has completed!', - 'DATABASE_UPDATE_CONTINUE' => 'Continue database update.', + 'DATABASE_UPDATE_CONTINUE' => 'Continue database update', 'DATABASE_UPDATE_INFO_OLD' => 'The database update file within the install directory is outdated. Please make sure you uploaded the correct version of the file.', 'DATABASE_UPDATE_NOT_COMPLETED' => 'The database update has not yet completed.', 'DELETE_USER_REMOVE' => 'Delete user and remove posts', -- cgit v1.2.1 From 8a6f3a58000b7d969bd9108f2bdb34203354d39b Mon Sep 17 00:00:00 2001 From: Andreas Fischer Date: Tue, 30 Jul 2013 01:54:11 +0200 Subject: [ticket/11524] Add another isset() to mitigate "Illegal string offset 'limit'" ... on PHP 5.4 or higher. PHPBB3-11524 --- phpBB/develop/mysql_upgrader.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'phpBB') diff --git a/phpBB/develop/mysql_upgrader.php b/phpBB/develop/mysql_upgrader.php index 05d279a099..17ce12e2bf 100644 --- a/phpBB/develop/mysql_upgrader.php +++ b/phpBB/develop/mysql_upgrader.php @@ -149,7 +149,8 @@ foreach ($schema_data as $table_name => $table_data) list($orig_column_type, $column_length) = explode(':', $column_data[0]); $column_type = sprintf($dbms_type_map['mysql_41'][$orig_column_type . ':'], $column_length); - if (isset($dbms_type_map['mysql_40'][$orig_column_type . ':']['limit'][0])) + if (isset($dbms_type_map['mysql_40'][$orig_column_type . ':']['limit']) && + isset($dbms_type_map['mysql_40'][$orig_column_type . ':']['limit'][0])) { switch ($dbms_type_map['mysql_40'][$orig_column_type . ':']['limit'][0]) { -- cgit v1.2.1 From 404f2881135373060086116003122a9f6d851adf Mon Sep 17 00:00:00 2001 From: Andreas Fischer Date: Tue, 30 Jul 2013 02:01:24 +0200 Subject: [ticket/11753] Update MySQL upgrader schema data. PHPBB3-11753 --- phpBB/develop/mysql_upgrader.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) (limited to 'phpBB') diff --git a/phpBB/develop/mysql_upgrader.php b/phpBB/develop/mysql_upgrader.php index 05d279a099..b3f40b2ca4 100644 --- a/phpBB/develop/mysql_upgrader.php +++ b/phpBB/develop/mysql_upgrader.php @@ -694,6 +694,24 @@ function get_schema_struct() ), ); + $schema_data['phpbb_login_attempts'] = array( + 'COLUMNS' => array( + 'attempt_ip' => array('VCHAR:40', ''), + 'attempt_browser' => array('VCHAR:150', ''), + 'attempt_forwarded_for' => array('VCHAR:255', ''), + 'attempt_time' => array('TIMESTAMP', 0), + 'user_id' => array('UINT', 0), + 'username' => array('VCHAR_UNI:255', 0), + 'username_clean' => array('VCHAR_CI', 0), + ), + 'KEYS' => array( + 'att_ip' => array('INDEX', array('attempt_ip', 'attempt_time')), + 'att_for' => array('INDEX', array('attempt_forwarded_for', 'attempt_time')), + 'att_time' => array('INDEX', array('attempt_time')), + 'user_id' => array('INDEX', 'user_id'), + ), + ); + $schema_data['phpbb_moderator_cache'] = array( 'COLUMNS' => array( 'forum_id' => array('UINT', 0), @@ -897,6 +915,7 @@ function get_schema_struct() 'field_default_value' => array('VCHAR_UNI', ''), 'field_validation' => array('VCHAR_UNI:20', ''), 'field_required' => array('BOOL', 0), + 'field_show_novalue' => array('BOOL', 0), 'field_show_on_reg' => array('BOOL', 0), 'field_show_on_vt' => array('BOOL', 0), 'field_show_profile' => array('BOOL', 0), -- cgit v1.2.1 From a3de463b3027733f0560a420fb1c61e5413a8957 Mon Sep 17 00:00:00 2001 From: Andreas Fischer Date: Tue, 30 Jul 2013 02:01:41 +0200 Subject: [ticket/11753] Remove ?> from MySQL Upgrader. PHPBB3-11753 --- phpBB/develop/mysql_upgrader.php | 2 -- 1 file changed, 2 deletions(-) (limited to 'phpBB') diff --git a/phpBB/develop/mysql_upgrader.php b/phpBB/develop/mysql_upgrader.php index b3f40b2ca4..6dd577ebf7 100644 --- a/phpBB/develop/mysql_upgrader.php +++ b/phpBB/develop/mysql_upgrader.php @@ -1415,5 +1415,3 @@ function get_schema_struct() return $schema_data; } - -?> \ No newline at end of file -- cgit v1.2.1 From c335edc038461449d86c2278cf414f304dcc735b Mon Sep 17 00:00:00 2001 From: Vjacheslav Trushkin Date: Tue, 30 Jul 2013 12:21:34 +0300 Subject: [ticket/11754] Remove styleswitcher leftovers PHPBB3-11754 --- phpBB/includes/functions.php | 2 -- phpBB/styles/prosilver/template/overall_header.html | 2 -- phpBB/styles/prosilver/template/simple_header.html | 2 -- 3 files changed, 6 deletions(-) (limited to 'phpBB') diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index 49f2e469bc..3db843ffd1 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -5390,8 +5390,6 @@ function page_header($page_title = '', $display_online_list = true, $item_id = 0 'T_UPLOAD' => $config['upload_path'], 'SITE_LOGO_IMG' => $user->img('site_logo'), - - 'A_COOKIE_SETTINGS' => addslashes('; path=' . $config['cookie_path'] . ((!$config['cookie_domain'] || $config['cookie_domain'] == 'localhost' || $config['cookie_domain'] == '127.0.0.1') ? '' : '; domain=' . $config['cookie_domain']) . ((!$config['cookie_secure']) ? '' : '; secure')), )); // application/xhtml+xml not used because of IE diff --git a/phpBB/styles/prosilver/template/overall_header.html b/phpBB/styles/prosilver/template/overall_header.html index ddbd917bd6..fcce0060f3 100644 --- a/phpBB/styles/prosilver/template/overall_header.html +++ b/phpBB/styles/prosilver/template/overall_header.html @@ -30,8 +30,6 @@ var on_page = '{ON_PAGE}'; var per_page = '{PER_PAGE}'; var base_url = '{A_BASE_URL}'; - var style_cookie = 'phpBBstyle'; - var style_cookie_settings = '{A_COOKIE_SETTINGS}'; var onload_functions = new Array(); var onunload_functions = new Array(); diff --git a/phpBB/styles/prosilver/template/simple_header.html b/phpBB/styles/prosilver/template/simple_header.html index 667698c371..5bdc539f40 100644 --- a/phpBB/styles/prosilver/template/simple_header.html +++ b/phpBB/styles/prosilver/template/simple_header.html @@ -14,10 +14,8 @@ var on_page = '{ON_PAGE}'; var per_page = '{PER_PAGE}'; var base_url = '{A_BASE_URL}'; - var style_cookie = 'phpBBstyle'; var onload_functions = new Array(); var onunload_functions = new Array(); - var style_cookie_settings = '{A_COOKIE_SETTINGS}'; /** * New function for handling multiple calls to window.onload and window.unload by pentapenguin -- cgit v1.2.1 From 212294382d2626012bb1caf0dde1392e59b1ca31 Mon Sep 17 00:00:00 2001 From: Vjacheslav Trushkin Date: Tue, 30 Jul 2013 12:32:14 +0300 Subject: [ticket/11688] Rename purge_dir to remove_dir PHPBB3-11688 --- phpBB/phpbb/cache/driver/file.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'phpBB') diff --git a/phpBB/phpbb/cache/driver/file.php b/phpBB/phpbb/cache/driver/file.php index 19596f5205..66a74ea4eb 100644 --- a/phpBB/phpbb/cache/driver/file.php +++ b/phpBB/phpbb/cache/driver/file.php @@ -223,7 +223,7 @@ class phpbb_cache_driver_file extends phpbb_cache_driver_base $filename = $fileInfo->getFilename(); if ($fileInfo->isDir()) { - $this->purge_dir($fileInfo->getPathname()); + $this->remove_dir($fileInfo->getPathname()); } elseif (strpos($filename, 'container_') === 0 || strpos($filename, 'url_matcher') === 0 || @@ -250,7 +250,7 @@ class phpbb_cache_driver_file extends phpbb_cache_driver_base /** * Remove directory */ - protected function purge_dir($dir) + protected function remove_dir($dir) { try { @@ -269,7 +269,7 @@ class phpbb_cache_driver_file extends phpbb_cache_driver_base } if ($fileInfo->isDir()) { - $this->purge_dir($fileInfo->getPathname()); + $this->remove_dir($fileInfo->getPathname()); } else { -- cgit v1.2.1 From 8295d0fb36df1c11af45f9062e17520d661e625c Mon Sep 17 00:00:00 2001 From: Bruno Ais Date: Tue, 30 Jul 2013 11:15:46 +0100 Subject: [ticket/11638] Variable names goof... sub-task of ticket PHPBB3-11635: find and fix all bypasses of generate_text_for_* PHPBB3-11638 --- phpBB/viewtopic.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB') diff --git a/phpBB/viewtopic.php b/phpBB/viewtopic.php index a24c40f697..e120322a4f 100644 --- a/phpBB/viewtopic.php +++ b/phpBB/viewtopic.php @@ -828,7 +828,7 @@ if (!empty($topic_data['poll_start'])) $poll_total += $poll_option['poll_option_total']; } - $parse_flags = ($row['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES; + $parse_flags = ($poll_info[0]['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES; for ($i = 0, $size = sizeof($poll_info); $i < $size; $i++) { -- cgit v1.2.1 From 95c603d545ade8ab37e81fd99f2b62765a54bcb0 Mon Sep 17 00:00:00 2001 From: Oliver Schramm Date: Tue, 30 Jul 2013 18:00:47 +0200 Subject: [ticket/8228] Fix still existing problems with code in firefox A user of a board mentioned that there are still some problems in firefox if php-highlighting is on. So I used a snippet which they worked on for weeks. Link to the snippet (in german): http://www.ongray-design.de/forum/viewtopic.php?t=541 PHPBB3-8228 --- phpBB/styles/prosilver/template/bbcode.html | 4 ++-- phpBB/styles/prosilver/template/forum_fn.js | 5 ++--- phpBB/styles/prosilver/theme/bidi.css | 2 +- phpBB/styles/prosilver/theme/colours.css | 6 +++--- phpBB/styles/prosilver/theme/content.css | 8 ++++---- phpBB/styles/prosilver/theme/print.css | 2 +- 6 files changed, 13 insertions(+), 14 deletions(-) (limited to 'phpBB') diff --git a/phpBB/styles/prosilver/template/bbcode.html b/phpBB/styles/prosilver/template/bbcode.html index 460d102c28..909c09df5a 100644 --- a/phpBB/styles/prosilver/template/bbcode.html +++ b/phpBB/styles/prosilver/template/bbcode.html @@ -12,8 +12,8 @@
-
{L_CODE}{L_COLON} {L_SELECT_ALL_CODE}

-
+

{L_CODE}{L_COLON} {L_SELECT_ALL_CODE}

+
diff --git a/phpBB/styles/prosilver/template/forum_fn.js b/phpBB/styles/prosilver/template/forum_fn.js index 42a68a2ef5..eccb12e827 100644 --- a/phpBB/styles/prosilver/template/forum_fn.js +++ b/phpBB/styles/prosilver/template/forum_fn.js @@ -187,7 +187,7 @@ function displayBlocks(c, e, t) { function selectCode(a) { // Get ID of code block - var e = a.parentNode.parentNode.getElementsByTagName('PRE')[0]; + var e = a.parentNode.parentNode.getElementsByTagName('CODE')[0]; var s, r; // Not IE and IE9+ @@ -205,8 +205,7 @@ function selectCode(a) { } r = document.createRange(); - r.setStart(e.firstChild, 0); - r.setEnd(e.lastChild, e.lastChild.textContent.length); + r.selectNodeContents(e); s.removeAllRanges(); s.addRange(r); } diff --git a/phpBB/styles/prosilver/theme/bidi.css b/phpBB/styles/prosilver/theme/bidi.css index f617428565..a921805327 100644 --- a/phpBB/styles/prosilver/theme/bidi.css +++ b/phpBB/styles/prosilver/theme/bidi.css @@ -422,7 +422,7 @@ margin-left: 0; } -.rtl blockquote dl.codebox { +.rtl blockquote .codebox { margin-right: 0; } diff --git a/phpBB/styles/prosilver/theme/colours.css b/phpBB/styles/prosilver/theme/colours.css index 801d607d9c..5548905ede 100644 --- a/phpBB/styles/prosilver/theme/colours.css +++ b/phpBB/styles/prosilver/theme/colours.css @@ -470,16 +470,16 @@ blockquote blockquote blockquote { } /* Code block */ -dl.codebox { +.codebox { background-color: #FFFFFF; border-color: #C9D2D8; } -dl.codebox dt { +.codebox p { border-bottom-color: #CCCCCC; } -dl.codebox pre { +.codebox code { color: #2E8B57; } diff --git a/phpBB/styles/prosilver/theme/content.css b/phpBB/styles/prosilver/theme/content.css index 0cab12910b..c56c7f9ef8 100644 --- a/phpBB/styles/prosilver/theme/content.css +++ b/phpBB/styles/prosilver/theme/content.css @@ -472,13 +472,13 @@ blockquote.uncited { } /* Code block */ -dl.codebox { +.codebox { padding: 3px; border: 1px solid transparent; font-size: 1em; } -dl.codebox dt { +.codebox p { text-transform: uppercase; border-bottom: 1px solid transparent; margin-bottom: 3px; @@ -487,11 +487,11 @@ dl.codebox dt { display: block; } -blockquote dl.codebox { +blockquote .codebox { margin-left: 0; } -dl.codebox pre { +.codebox code { /* Also see tweaks.css */ overflow: auto; display: block; diff --git a/phpBB/styles/prosilver/theme/print.css b/phpBB/styles/prosilver/theme/print.css index bc3ca80fdc..88de620493 100644 --- a/phpBB/styles/prosilver/theme/print.css +++ b/phpBB/styles/prosilver/theme/print.css @@ -136,4 +136,4 @@ div.spacer { clear: both; } /* Accessibility tweaks: Mozilla.org */ .skip_link { display: none; } -dl.codebox dt { display: none; } +.codebox p { display: none; } -- cgit v1.2.1 From 5eb321d3113136f5e6cbc1f0ad911636f93f94df Mon Sep 17 00:00:00 2001 From: Vjacheslav Trushkin Date: Tue, 30 Jul 2013 21:02:40 +0300 Subject: [ticket/11688] Fix docblock PHPBB3-11688 --- phpBB/phpbb/cache/driver/file.php | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'phpBB') diff --git a/phpBB/phpbb/cache/driver/file.php b/phpBB/phpbb/cache/driver/file.php index 66a74ea4eb..944dfd6541 100644 --- a/phpBB/phpbb/cache/driver/file.php +++ b/phpBB/phpbb/cache/driver/file.php @@ -249,6 +249,10 @@ class phpbb_cache_driver_file extends phpbb_cache_driver_base /** * Remove directory + * + * @param string $dir Directory to remove + * + * @return null */ protected function remove_dir($dir) { @@ -267,6 +271,7 @@ class phpbb_cache_driver_file extends phpbb_cache_driver_base { continue; } + if ($fileInfo->isDir()) { $this->remove_dir($fileInfo->getPathname()); -- cgit v1.2.1 From 8892205644a9423998441472b48a29dcea321360 Mon Sep 17 00:00:00 2001 From: Oliver Schramm Date: Tue, 30 Jul 2013 22:32:05 +0200 Subject: [ticket/11757] Fix typo in signature_module_auth migration PHPBB3-11757 --- phpBB/phpbb/db/migration/data/310/signature_module_auth.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB') diff --git a/phpBB/phpbb/db/migration/data/310/signature_module_auth.php b/phpBB/phpbb/db/migration/data/310/signature_module_auth.php index e4fbb27bcb..02cd70059a 100644 --- a/phpBB/phpbb/db/migration/data/310/signature_module_auth.php +++ b/phpBB/phpbb/db/migration/data/310/signature_module_auth.php @@ -17,7 +17,7 @@ class phpbb_db_migration_data_310_signature_module_auth extends phpbb_db_migrati AND module_basename = 'ucp_profile' AND module_mode = 'signature'"; $result = $this->db->sql_query($sql); - $module_auth = $this->db_sql_fetchfield('module_auth'); + $module_auth = $this->db->sql_fetchfield('module_auth'); $this->db->sql_freeresult($result); return $module_auth === 'acl_u_sig' || $module_auth === false; -- cgit v1.2.1 From 715e408ee6765f84b812636e808786f73f43699b Mon Sep 17 00:00:00 2001 From: Oliver Schramm Date: Wed, 31 Jul 2013 00:00:50 +0200 Subject: [ticket/11464] Add missing and remove unnecessary database entry PHPBB3-11464 --- phpBB/install/schemas/schema_data.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB') diff --git a/phpBB/install/schemas/schema_data.sql b/phpBB/install/schemas/schema_data.sql index d03fdf9de4..0a31b89aab 100644 --- a/phpBB/install/schemas/schema_data.sql +++ b/phpBB/install/schemas/schema_data.sql @@ -170,7 +170,7 @@ INSERT INTO phpbb_config (config_name, config_value) VALUES ('limit_search_load' INSERT INTO phpbb_config (config_name, config_value) VALUES ('load_anon_lastread', '0'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('load_birthdays', '1'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('load_cpf_memberlist', '0'); -INSERT INTO phpbb_config (config_name, config_value) VALUES ('load_cpf_profile', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('load_cpf_pm', '0'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('load_cpf_viewprofile', '1'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('load_cpf_viewtopic', '0'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('load_db_lastread', '1'); -- cgit v1.2.1 From 6f883b6791e1bc9b168c98d4a95e9bbed6731a74 Mon Sep 17 00:00:00 2001 From: Oliver Schramm Date: Wed, 31 Jul 2013 14:04:50 +0200 Subject: [ticket/10037] Fix table in subsilver2 Thanks, nickvergessen! ;-) PHPBB3-10037 --- .../subsilver2/template/ucp_profile_signature.html | 42 ++++++++++------------ 1 file changed, 18 insertions(+), 24 deletions(-) (limited to 'phpBB') diff --git a/phpBB/styles/subsilver2/template/ucp_profile_signature.html b/phpBB/styles/subsilver2/template/ucp_profile_signature.html index 8588a99438..54e8aaa723 100644 --- a/phpBB/styles/subsilver2/template/ucp_profile_signature.html +++ b/phpBB/styles/subsilver2/template/ucp_profile_signature.html @@ -1,5 +1,12 @@ + + @@ -18,20 +25,10 @@ - -- cgit v1.2.1 From 07bc935efea32de9cbef7039730d910b1e2befea Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Wed, 31 Jul 2013 23:03:32 +0200 Subject: [ticket/11751] Add mcp modules for softdelete on update PHPBB3-11751 --- .../migration/data/310/softdelete_mcp_modules.php | 55 ++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 phpBB/phpbb/db/migration/data/310/softdelete_mcp_modules.php (limited to 'phpBB') diff --git a/phpBB/phpbb/db/migration/data/310/softdelete_mcp_modules.php b/phpBB/phpbb/db/migration/data/310/softdelete_mcp_modules.php new file mode 100644 index 0000000000..f80f55d19a --- /dev/null +++ b/phpBB/phpbb/db/migration/data/310/softdelete_mcp_modules.php @@ -0,0 +1,55 @@ +db->sql_query($sql); + $module_id = $this->db->sql_fetchfield('module_id'); + $this->db->sql_freeresult($result); + + return $module_id !== false; + } + + static public function depends_on() + { + return array( + 'phpbb_db_migration_data_310_dev', + 'phpbb_db_migration_data_310_softdelete_p2', + ); + } + + public function update_data() + { + return array( + array('module.add', array( + 'mcp', + 'MCP_QUEUE', + array( + 'module_basename' => 'mcp_queue', + 'modes' => array('deleted_topics'), + ), + )), + array('module.add', array( + 'mcp', + 'MCP_QUEUE', + array( + 'module_basename' => 'mcp_queue', + 'modes' => array('deleted_posts'), + ), + )), + ); + } +} -- cgit v1.2.1 From c2aff70cf561c1787be6cdaa193432d0cbdf432d Mon Sep 17 00:00:00 2001 From: Bruno Ais Date: Thu, 1 Aug 2013 10:03:04 +0100 Subject: [ticket/11655] Use $parse_flags sub-task of ticket PHPBB3-11635: find and fix all bypasses of generate_text_for_* PHPBB3-11655 --- phpBB/includes/ucp/ucp_pm_viewmessage.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'phpBB') diff --git a/phpBB/includes/ucp/ucp_pm_viewmessage.php b/phpBB/includes/ucp/ucp_pm_viewmessage.php index 52a28e3552..8e7ae49fa4 100644 --- a/phpBB/includes/ucp/ucp_pm_viewmessage.php +++ b/phpBB/includes/ucp/ucp_pm_viewmessage.php @@ -76,7 +76,8 @@ function view_message($id, $mode, $folder_id, $msg_id, $folder, $message_row) $user_info = get_user_information($author_id, $message_row); // Parse the message and subject - $message = generate_text_for_display($message_row['message_text'], $message_row['bbcode_uid'], $message_row['bbcode_bitfield'], ($message_row['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES, true); + $parse_flags = ($message_row['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES + $message = generate_text_for_display($message_row['message_text'], $message_row['bbcode_uid'], $message_row['bbcode_bitfield'], $parse_flags, true); // Replace naughty words such as farty pants $message_row['message_subject'] = censor_text($message_row['message_subject']); -- cgit v1.2.1 From 776773522ba129c0e7d0918b2f6beccd4c044dbe Mon Sep 17 00:00:00 2001 From: Bruno Ais Date: Thu, 1 Aug 2013 10:07:58 +0100 Subject: [ticket/11653] Use $parse_flags sub-task of ticket PHPBB3-11635: find and fix all bypasses of generate_text_for_* PHPBB3-11653 --- phpBB/includes/mcp/mcp_topic.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'phpBB') diff --git a/phpBB/includes/mcp/mcp_topic.php b/phpBB/includes/mcp/mcp_topic.php index 3491f37bcb..5014879b02 100644 --- a/phpBB/includes/mcp/mcp_topic.php +++ b/phpBB/includes/mcp/mcp_topic.php @@ -213,7 +213,8 @@ function mcp_topic_view($id, $mode, $action) $message = $row['post_text']; $post_subject = ($row['post_subject'] != '') ? $row['post_subject'] : $topic_info['topic_title']; - $message = generate_text_for_display($message, $row['bbcode_uid'], $row['bbcode_bitfield'], ($row['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES, false); + $parse_flags = ($row['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES; + $message = generate_text_for_display($message, $row['bbcode_uid'], $row['bbcode_bitfield'], $parse_flags, false); if (!empty($attachments[$row['post_id']])) { -- cgit v1.2.1 From c806375828c01c011915f1438f718349aaaaf282 Mon Sep 17 00:00:00 2001 From: Bruno Ais Date: Thu, 1 Aug 2013 10:09:11 +0100 Subject: [ticket/11653] Missing ";" sub-task of ticket PHPBB3-11635: find and fix all bypasses of generate_text_for_* PHPBB3-11653 --- phpBB/includes/ucp/ucp_pm_viewmessage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB') diff --git a/phpBB/includes/ucp/ucp_pm_viewmessage.php b/phpBB/includes/ucp/ucp_pm_viewmessage.php index 8e7ae49fa4..79c4297858 100644 --- a/phpBB/includes/ucp/ucp_pm_viewmessage.php +++ b/phpBB/includes/ucp/ucp_pm_viewmessage.php @@ -76,7 +76,7 @@ function view_message($id, $mode, $folder_id, $msg_id, $folder, $message_row) $user_info = get_user_information($author_id, $message_row); // Parse the message and subject - $parse_flags = ($message_row['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES + $parse_flags = ($message_row['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES; $message = generate_text_for_display($message_row['message_text'], $message_row['bbcode_uid'], $message_row['bbcode_bitfield'], $parse_flags, true); // Replace naughty words such as farty pants -- cgit v1.2.1 From ea6938d3e518b636529238ab9ffd5b57b1a19241 Mon Sep 17 00:00:00 2001 From: Bruno Ais Date: Thu, 1 Aug 2013 10:11:08 +0100 Subject: [ticket/11643] Use $parse_flags sub-task of ticket PHPBB3-11635: find and fix all bypasses of generate_text_for_* PHPBB3-11643 --- phpBB/includes/mcp/mcp_queue.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'phpBB') diff --git a/phpBB/includes/mcp/mcp_queue.php b/phpBB/includes/mcp/mcp_queue.php index 2c95dc6a67..190fccab9a 100644 --- a/phpBB/includes/mcp/mcp_queue.php +++ b/phpBB/includes/mcp/mcp_queue.php @@ -132,7 +132,8 @@ class mcp_queue $post_unread = (isset($topic_tracking_info[$post_info['topic_id']]) && $post_info['post_time'] > $topic_tracking_info[$post_info['topic_id']]) ? true : false; // Process message, leave it uncensored - $message = generate_text_for_display($post_info['post_text'], $post_info['bbcode_uid'], $post_info['bbcode_bitfield'], ($post_info['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES, false); + $parse_flags = ($post_info['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES; + $message = generate_text_for_display($post_info['post_text'], $post_info['bbcode_uid'], $post_info['bbcode_bitfield'], $parse_flags, false); if ($post_info['post_attachment'] && $auth->acl_get('u_download') && $auth->acl_get('f_download', $post_info['forum_id'])) { -- cgit v1.2.1 From a302a09ffbe174e6720ce250121d34ba8b6fd626 Mon Sep 17 00:00:00 2001 From: Bruno Ais Date: Thu, 1 Aug 2013 10:12:58 +0100 Subject: [ticket/11642] Use $parse_flags sub-task of ticket PHPBB3-11635: find and fix all bypasses of generate_text_for_* PHPBB3-11642 --- phpBB/includes/mcp/mcp_post.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'phpBB') diff --git a/phpBB/includes/mcp/mcp_post.php b/phpBB/includes/mcp/mcp_post.php index e8768957e0..e9f6ce0471 100644 --- a/phpBB/includes/mcp/mcp_post.php +++ b/phpBB/includes/mcp/mcp_post.php @@ -125,7 +125,8 @@ function mcp_post_details($id, $mode, $action) $post_unread = (isset($topic_tracking_info[$post_info['topic_id']]) && $post_info['post_time'] > $topic_tracking_info[$post_info['topic_id']]) ? true : false; // Process message, leave it uncensored - $message = generate_text_for_display($post_info['post_text'], $post_info['bbcode_uid'], $post_info['bbcode_bitfield'], ($post_info['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES, false); + $parse_flags = ($post_info['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES; + $message = generate_text_for_display($post_info['post_text'], $post_info['bbcode_uid'], $post_info['bbcode_bitfield'], $parse_flags, false); if ($post_info['post_attachment'] && $auth->acl_get('u_download') && $auth->acl_get('f_download', $post_info['forum_id'])) { -- cgit v1.2.1 From 3ae33910fc600ae86f0036370958bd7ec19e7ab9 Mon Sep 17 00:00:00 2001 From: Bruno Ais Date: Thu, 1 Aug 2013 10:14:34 +0100 Subject: [ticket/11653] Use $parse_flags sub-task of ticket PHPBB3-11635: find and fix all bypasses of generate_text_for_* PHPBB3-11653 --- phpBB/includes/ucp/ucp_pm_viewmessage.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'phpBB') diff --git a/phpBB/includes/ucp/ucp_pm_viewmessage.php b/phpBB/includes/ucp/ucp_pm_viewmessage.php index 79c4297858..c7b4489daf 100644 --- a/phpBB/includes/ucp/ucp_pm_viewmessage.php +++ b/phpBB/includes/ucp/ucp_pm_viewmessage.php @@ -151,7 +151,8 @@ function view_message($id, $mode, $folder_id, $msg_id, $folder, $message_row) // End signature parsing, only if needed if ($signature) { - $signature = generate_text_for_display($signature, $user_info['user_sig_bbcode_uid'], $user_info['user_sig_bbcode_bitfield'], ($user_info['user_sig_bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES, true); + $parse_flags = ($user_info['user_sig_bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES; + $signature = generate_text_for_display($signature, $user_info['user_sig_bbcode_uid'], $user_info['user_sig_bbcode_bitfield'], $parse_flags, true); } $url = append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm'); -- cgit v1.2.1 From 2f251972790e7836d9c35eb2f5b49fda97f736d6 Mon Sep 17 00:00:00 2001 From: Bruno Ais Date: Thu, 1 Aug 2013 10:16:33 +0100 Subject: [ticket/11641] Use $parse_flags sub-task of ticket PHPBB3-11635: find and fix all bypasses of generate_text_for_* PHPBB3-11641 --- phpBB/includes/mcp/mcp_pm_reports.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'phpBB') diff --git a/phpBB/includes/mcp/mcp_pm_reports.php b/phpBB/includes/mcp/mcp_pm_reports.php index dc953aae33..cb61b25174 100644 --- a/phpBB/includes/mcp/mcp_pm_reports.php +++ b/phpBB/includes/mcp/mcp_pm_reports.php @@ -115,7 +115,8 @@ class mcp_pm_reports } // Process message, leave it uncensored - $message = generate_text_for_display($pm_info['message_text'], $pm_info['bbcode_uid'], $pm_info['bbcode_bitfield'], ($pm_info['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES, false); + $parse_flags = ($pm_info['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES; + $message = generate_text_for_display($pm_info['message_text'], $pm_info['bbcode_uid'], $pm_info['bbcode_bitfield'], $parse_flags, false); $report['report_text'] = make_clickable(bbcode_nl2br($report['report_text'])); -- cgit v1.2.1 From ea8f584de9d572f8c58b26acd1fd7551c42ab59c Mon Sep 17 00:00:00 2001 From: Andreas Fischer Date: Thu, 1 Aug 2013 17:26:34 +0200 Subject: [prep-release-3.0.12] Bumping version number for 3.0.12-RC2. --- phpBB/includes/constants.php | 2 +- phpBB/install/database_update.php | 8 +++++++- phpBB/install/schemas/schema_data.sql | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) (limited to 'phpBB') diff --git a/phpBB/includes/constants.php b/phpBB/includes/constants.php index 0a853adb9b..09e1e50b8d 100644 --- a/phpBB/includes/constants.php +++ b/phpBB/includes/constants.php @@ -25,7 +25,7 @@ if (!defined('IN_PHPBB')) */ // phpBB Version -define('PHPBB_VERSION', '3.0.12-RC1'); +define('PHPBB_VERSION', '3.0.12-RC2'); // QA-related // define('PHPBB_QA', 1); diff --git a/phpBB/install/database_update.php b/phpBB/install/database_update.php index 5cb410bf29..59f795e37e 100644 --- a/phpBB/install/database_update.php +++ b/phpBB/install/database_update.php @@ -8,7 +8,7 @@ * */ -define('UPDATES_TO_VERSION', '3.0.12-RC1'); +define('UPDATES_TO_VERSION', '3.0.12-RC2'); // Enter any version to update from to test updates. The version within the db will not be updated. define('DEBUG_FROM_VERSION', false); @@ -1005,6 +1005,8 @@ function database_update_info() '3.0.11-RC2' => array(), // No changes from 3.0.11 to 3.0.12-RC1 '3.0.11' => array(), + // No changes from 3.0.12-RC1 to 3.0.12-RC2 + '3.0.12-RC1' => array(), /** @todo DROP LOGIN_ATTEMPT_TABLE.attempt_id in 3.0.13-RC1 */ ); @@ -2236,6 +2238,10 @@ function change_database_data(&$no_updates, $version) $no_updates = false; break; + + // No changes from 3.0.12-RC1 to 3.0.12-RC2 + case '3.0.12-RC1': + break; } } diff --git a/phpBB/install/schemas/schema_data.sql b/phpBB/install/schemas/schema_data.sql index 04b92b19c6..cfe3c0c4cb 100644 --- a/phpBB/install/schemas/schema_data.sql +++ b/phpBB/install/schemas/schema_data.sql @@ -246,7 +246,7 @@ INSERT INTO phpbb_config (config_name, config_value) VALUES ('topics_per_page', INSERT INTO phpbb_config (config_name, config_value) VALUES ('tpl_allow_php', '0'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('upload_icons_path', 'images/upload_icons'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('upload_path', 'files'); -INSERT INTO phpbb_config (config_name, config_value) VALUES ('version', '3.0.12-RC1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('version', '3.0.12-RC2'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('warnings_expire_days', '90'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('warnings_gc', '14400'); -- cgit v1.2.1 From 0a73d64b9772971b7e43e989c659184ee20c5bc6 Mon Sep 17 00:00:00 2001 From: Andreas Fischer Date: Thu, 1 Aug 2013 17:28:21 +0200 Subject: [prep-release-3.0.12] Update Changelog for 3.0.12-RC2 release. --- phpBB/docs/CHANGELOG.html | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'phpBB') diff --git a/phpBB/docs/CHANGELOG.html b/phpBB/docs/CHANGELOG.html index 44cc49d8c4..f2d5ddc212 100644 --- a/phpBB/docs/CHANGELOG.html +++ b/phpBB/docs/CHANGELOG.html @@ -190,6 +190,8 @@
  • [PHPBB3-11662] - "occured" should be "occurred"
  • [PHPBB3-11670] - Replace trademark â„¢ with ® on "Welcome to phpBB" install page
  • [PHPBB3-11674] - Do not include vendor folder if there are no dependencies.
  • +
  • [PHPBB3-11524] - MySQL Upgrader throws warnings on PHP 5.4
  • +
  • [PHPBB3-11720] - Reporting posts leads to white page error
  • Improvement

      @@ -213,6 +215,7 @@
    • [PHPBB3-11294] - Update extension list in running tests doc
    • [PHPBB3-11368] - Latest pm reports row count
    • [PHPBB3-11583] - InnoDB supports FULLTEXT index since MySQL 5.6.4.
    • +
    • [PHPBB3-11740] - Update link in FAQ to Ideas Centre

    Sub-task

      @@ -238,6 +241,8 @@
    • [PHPBB3-11529] - Rename RUNNING_TESTS file to .md file to render it on GitHub
    • [PHPBB3-11576] - Make phpBB Test Suite MySQL behave at least as strict as phpBB MySQL driver
    • [PHPBB3-11671] - Add phing/phing to composer.json
    • +
    • [PHPBB3-11752] - Update phpBB.com URLs to https in email templates
    • +
    • [PHPBB3-11753] - Upgrade mysql_upgrader.php schema data.

    1.ii. Changes since 3.0.10

    -- cgit v1.2.1 From 7d90f0a8a00ac66f6a561766e25fb4ea16a4111b Mon Sep 17 00:00:00 2001 From: Oliver Schramm Date: Fri, 2 Aug 2013 02:49:28 +0200 Subject: [ticket/11759] Change lowercase to uppercase PHPBB3-11759 --- phpBB/phpbb/db/migration/data/30x/3_0_10_rc1.php | 4 ++-- phpBB/phpbb/db/migration/data/30x/3_0_10_rc2.php | 4 ++-- phpBB/phpbb/db/migration/data/30x/3_0_10_rc3.php | 4 ++-- phpBB/phpbb/db/migration/data/30x/3_0_11_rc1.php | 4 ++-- phpBB/phpbb/db/migration/data/30x/3_0_11_rc2.php | 4 ++-- phpBB/phpbb/db/migration/data/30x/3_0_12_rc1.php | 4 ++-- phpBB/phpbb/db/migration/data/30x/3_0_1_rc1.php | 4 ++-- phpBB/phpbb/db/migration/data/30x/3_0_2_rc1.php | 4 ++-- phpBB/phpbb/db/migration/data/30x/3_0_2_rc2.php | 4 ++-- phpBB/phpbb/db/migration/data/30x/3_0_3_rc1.php | 4 ++-- phpBB/phpbb/db/migration/data/30x/3_0_4_rc1.php | 4 ++-- phpBB/phpbb/db/migration/data/30x/3_0_5_rc1.php | 2 +- phpBB/phpbb/db/migration/data/30x/3_0_5_rc1part2.php | 4 ++-- phpBB/phpbb/db/migration/data/30x/3_0_6_rc1.php | 4 ++-- phpBB/phpbb/db/migration/data/30x/3_0_6_rc2.php | 4 ++-- phpBB/phpbb/db/migration/data/30x/3_0_6_rc3.php | 4 ++-- phpBB/phpbb/db/migration/data/30x/3_0_6_rc4.php | 4 ++-- phpBB/phpbb/db/migration/data/30x/3_0_7_rc1.php | 4 ++-- phpBB/phpbb/db/migration/data/30x/3_0_7_rc2.php | 4 ++-- phpBB/phpbb/db/migration/data/30x/3_0_8_rc1.php | 4 ++-- phpBB/phpbb/db/migration/data/30x/3_0_9_rc1.php | 4 ++-- phpBB/phpbb/db/migration/data/30x/3_0_9_rc2.php | 4 ++-- phpBB/phpbb/db/migration/data/30x/3_0_9_rc3.php | 4 ++-- phpBB/phpbb/db/migration/data/30x/3_0_9_rc4.php | 4 ++-- 24 files changed, 47 insertions(+), 47 deletions(-) (limited to 'phpBB') diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_10_rc1.php b/phpBB/phpbb/db/migration/data/30x/3_0_10_rc1.php index 0ed05812dc..459b423736 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_10_rc1.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_10_rc1.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_10_rc1 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.10-rc1', '>='); + return version_compare($this->config['version'], '3.0.10-RC1', '>='); } static public function depends_on() @@ -24,7 +24,7 @@ class phpbb_db_migration_data_30x_3_0_10_rc1 extends phpbb_db_migration return array( array('config.add', array('email_max_chunk_size', 50)), - array('config.update', array('version', '3.0.10-rc1')), + array('config.update', array('version', '3.0.10-RC1')), ); } } diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_10_rc2.php b/phpBB/phpbb/db/migration/data/30x/3_0_10_rc2.php index b14b3b00aa..8d21cab45d 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_10_rc2.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_10_rc2.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_10_rc2 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.10-rc2', '>='); + return version_compare($this->config['version'], '3.0.10-RC2', '>='); } static public function depends_on() @@ -22,7 +22,7 @@ class phpbb_db_migration_data_30x_3_0_10_rc2 extends phpbb_db_migration public function update_data() { return array( - array('config.update', array('version', '3.0.10-rc2')), + array('config.update', array('version', '3.0.10-RC2')), ); } } diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_10_rc3.php b/phpBB/phpbb/db/migration/data/30x/3_0_10_rc3.php index 473057d65d..296c3c40af 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_10_rc3.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_10_rc3.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_10_rc3 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.10-rc3', '>='); + return version_compare($this->config['version'], '3.0.10-RC3', '>='); } static public function depends_on() @@ -22,7 +22,7 @@ class phpbb_db_migration_data_30x_3_0_10_rc3 extends phpbb_db_migration public function update_data() { return array( - array('config.update', array('version', '3.0.10-rc3')), + array('config.update', array('version', '3.0.10-RC3')), ); } } diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_11_rc1.php b/phpBB/phpbb/db/migration/data/30x/3_0_11_rc1.php index dddfc0e0e7..3a3908258f 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_11_rc1.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_11_rc1.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_11_rc1 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.11-rc1', '>='); + return version_compare($this->config['version'], '3.0.11-RC1', '>='); } static public function depends_on() @@ -25,7 +25,7 @@ class phpbb_db_migration_data_30x_3_0_11_rc1 extends phpbb_db_migration array('custom', array(array(&$this, 'cleanup_deactivated_styles'))), array('custom', array(array(&$this, 'delete_orphan_private_messages'))), - array('config.update', array('version', '3.0.11-rc1')), + array('config.update', array('version', '3.0.11-RC1')), ); } diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_11_rc2.php b/phpBB/phpbb/db/migration/data/30x/3_0_11_rc2.php index fac8523e8c..4b1b5642ce 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_11_rc2.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_11_rc2.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_11_rc2 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.11-rc2', '>='); + return version_compare($this->config['version'], '3.0.11-RC2', '>='); } static public function depends_on() @@ -44,7 +44,7 @@ class phpbb_db_migration_data_30x_3_0_11_rc2 extends phpbb_db_migration public function update_data() { return array( - array('config.update', array('version', '3.0.11-rc2')), + array('config.update', array('version', '3.0.11-RC2')), ); } } diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_12_rc1.php b/phpBB/phpbb/db/migration/data/30x/3_0_12_rc1.php index 6a31a51201..97331e9bb2 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_12_rc1.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_12_rc1.php @@ -13,7 +13,7 @@ class phpbb_db_migration_data_30x_3_0_12_rc1 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.12-rc1', '>='); + return version_compare($this->config['version'], '3.0.12-RC1', '>='); } static public function depends_on() @@ -28,7 +28,7 @@ class phpbb_db_migration_data_30x_3_0_12_rc1 extends phpbb_db_migration array('custom', array(array(&$this, 'update_bots'))), array('custom', array(array(&$this, 'disable_bots_from_receiving_pms'))), - array('config.update', array('version', '3.0.12-rc1')), + array('config.update', array('version', '3.0.12-RC1')), ); } diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_1_rc1.php b/phpBB/phpbb/db/migration/data/30x/3_0_1_rc1.php index 562ccf077c..761ed5d2ec 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_1_rc1.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_1_rc1.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_1_rc1 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.1-rc1', '>='); + return version_compare($this->config['version'], '3.0.1-RC1', '>='); } public function update_schema() @@ -74,7 +74,7 @@ class phpbb_db_migration_data_30x_3_0_1_rc1 extends phpbb_db_migration array('custom', array(array(&$this, 'fix_unset_last_view_time'))), array('custom', array(array(&$this, 'reset_smiley_size'))), - array('config.update', array('version', '3.0.1-rc1')), + array('config.update', array('version', '3.0.1-RC1')), ); } diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_2_rc1.php b/phpBB/phpbb/db/migration/data/30x/3_0_2_rc1.php index a960e90765..a84f3c2d92 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_2_rc1.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_2_rc1.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_2_rc1 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.2-rc1', '>='); + return version_compare($this->config['version'], '3.0.2-RC1', '>='); } static public function depends_on() @@ -26,7 +26,7 @@ class phpbb_db_migration_data_30x_3_0_2_rc1 extends phpbb_db_migration array('config.add', array('check_attachment_content', '1')), array('config.add', array('mime_triggers', 'body|head|html|img|plaintext|a href|pre|script|table|title')), - array('config.update', array('version', '3.0.2-rc1')), + array('config.update', array('version', '3.0.2-RC1')), ); } } diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_2_rc2.php b/phpBB/phpbb/db/migration/data/30x/3_0_2_rc2.php index 8917dfea77..33bacc077f 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_2_rc2.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_2_rc2.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_2_rc2 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.2-rc2', '>='); + return version_compare($this->config['version'], '3.0.2-RC2', '>='); } static public function depends_on() @@ -74,7 +74,7 @@ class phpbb_db_migration_data_30x_3_0_2_rc2 extends phpbb_db_migration public function update_data() { return array( - array('config.update', array('version', '3.0.2-rc2')), + array('config.update', array('version', '3.0.2-RC2')), ); } } diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_3_rc1.php b/phpBB/phpbb/db/migration/data/30x/3_0_3_rc1.php index 4b102e1a2e..69433f386e 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_3_rc1.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_3_rc1.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_3_rc1 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.3-rc1', '>='); + return version_compare($this->config['version'], '3.0.3-RC1', '>='); } static public function depends_on() @@ -60,7 +60,7 @@ class phpbb_db_migration_data_30x_3_0_3_rc1 extends phpbb_db_migration array('permission.add', array('u_masspm_group', true, 'u_masspm')), array('custom', array(array(&$this, 'correct_acp_email_permissions'))), - array('config.update', array('version', '3.0.3-rc1')), + array('config.update', array('version', '3.0.3-RC1')), ); } diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_4_rc1.php b/phpBB/phpbb/db/migration/data/30x/3_0_4_rc1.php index 8ad75a557b..e45bd3eeee 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_4_rc1.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_4_rc1.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_4_rc1 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.4-rc1', '>='); + return version_compare($this->config['version'], '3.0.4-RC1', '>='); } static public function depends_on() @@ -76,7 +76,7 @@ class phpbb_db_migration_data_30x_3_0_4_rc1 extends phpbb_db_migration return array( array('custom', array(array(&$this, 'update_custom_profile_fields'))), - array('config.update', array('version', '3.0.4-rc1')), + array('config.update', array('version', '3.0.4-RC1')), ); } diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_5_rc1.php b/phpBB/phpbb/db/migration/data/30x/3_0_5_rc1.php index ea17cc1e31..62ae7bff1a 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_5_rc1.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_5_rc1.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_5_rc1 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.5-rc1', '>='); + return version_compare($this->config['version'], '3.0.5-RC1', '>='); } static public function depends_on() diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_5_rc1part2.php b/phpBB/phpbb/db/migration/data/30x/3_0_5_rc1part2.php index 8538347b1a..d72176489b 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_5_rc1part2.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_5_rc1part2.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_5_rc1part2 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.5-rc1', '>='); + return version_compare($this->config['version'], '3.0.5-RC1', '>='); } static public function depends_on() @@ -36,7 +36,7 @@ class phpbb_db_migration_data_30x_3_0_5_rc1part2 extends phpbb_db_migration public function update_data() { return array( - array('config.update', array('version', '3.0.5-rc1')), + array('config.update', array('version', '3.0.5-RC1')), ); } } diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_6_rc1.php b/phpBB/phpbb/db/migration/data/30x/3_0_6_rc1.php index 38c282ebf0..25f4995b0e 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_6_rc1.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_6_rc1.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_6_rc1 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.6-rc1', '>='); + return version_compare($this->config['version'], '3.0.6-RC1', '>='); } static public function depends_on() @@ -185,7 +185,7 @@ class phpbb_db_migration_data_30x_3_0_6_rc1 extends phpbb_db_migration array('custom', array(array(&$this, 'add_newly_registered_group'))), array('custom', array(array(&$this, 'set_user_options_default'))), - array('config.update', array('version', '3.0.6-rc1')), + array('config.update', array('version', '3.0.6-RC1')), ); } diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_6_rc2.php b/phpBB/phpbb/db/migration/data/30x/3_0_6_rc2.php index a939dbd489..d5d14ab05d 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_6_rc2.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_6_rc2.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_6_rc2 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.6-rc2', '>='); + return version_compare($this->config['version'], '3.0.6-RC2', '>='); } static public function depends_on() @@ -22,7 +22,7 @@ class phpbb_db_migration_data_30x_3_0_6_rc2 extends phpbb_db_migration public function update_data() { return array( - array('config.update', array('version', '3.0.6-rc2')), + array('config.update', array('version', '3.0.6-RC2')), ); } } diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_6_rc3.php b/phpBB/phpbb/db/migration/data/30x/3_0_6_rc3.php index b3f09d8ab8..f3f1fb42f4 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_6_rc3.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_6_rc3.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_6_rc3 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.6-rc3', '>='); + return version_compare($this->config['version'], '3.0.6-RC3', '>='); } static public function depends_on() @@ -24,7 +24,7 @@ class phpbb_db_migration_data_30x_3_0_6_rc3 extends phpbb_db_migration return array( array('custom', array(array(&$this, 'update_cp_fields'))), - array('config.update', array('version', '3.0.6-rc3')), + array('config.update', array('version', '3.0.6-RC3')), ); } diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_6_rc4.php b/phpBB/phpbb/db/migration/data/30x/3_0_6_rc4.php index fc2923f99b..6138ef351d 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_6_rc4.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_6_rc4.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_6_rc4 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.6-rc4', '>='); + return version_compare($this->config['version'], '3.0.6-RC4', '>='); } static public function depends_on() @@ -22,7 +22,7 @@ class phpbb_db_migration_data_30x_3_0_6_rc4 extends phpbb_db_migration public function update_data() { return array( - array('config.update', array('version', '3.0.6-rc4')), + array('config.update', array('version', '3.0.6-RC4')), ); } } diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_7_rc1.php b/phpBB/phpbb/db/migration/data/30x/3_0_7_rc1.php index ffebf66f2d..be8f9045f4 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_7_rc1.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_7_rc1.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_7_rc1 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.7-rc1', '>='); + return version_compare($this->config['version'], '3.0.7-RC1', '>='); } static public function depends_on() @@ -62,7 +62,7 @@ class phpbb_db_migration_data_30x_3_0_7_rc1 extends phpbb_db_migration array('config.add', array('feed_topics_active', $this->config['feed_overall_topics'])), array('custom', array(array(&$this, 'delete_text_templates'))), - array('config.update', array('version', '3.0.7-rc1')), + array('config.update', array('version', '3.0.7-RC1')), ); } diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_7_rc2.php b/phpBB/phpbb/db/migration/data/30x/3_0_7_rc2.php index 55bc2bc679..0e43229f13 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_7_rc2.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_7_rc2.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_7_rc2 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.7-rc2', '>='); + return version_compare($this->config['version'], '3.0.7-RC2', '>='); } static public function depends_on() @@ -24,7 +24,7 @@ class phpbb_db_migration_data_30x_3_0_7_rc2 extends phpbb_db_migration return array( array('custom', array(array(&$this, 'update_email_hash'))), - array('config.update', array('version', '3.0.7-rc2')), + array('config.update', array('version', '3.0.7-RC2')), ); } diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_8_rc1.php b/phpBB/phpbb/db/migration/data/30x/3_0_8_rc1.php index aeff35333e..ff7824fa3b 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_8_rc1.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_8_rc1.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_8_rc1 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.8-rc1', '>='); + return version_compare($this->config['version'], '3.0.8-RC1', '>='); } static public function depends_on() @@ -38,7 +38,7 @@ class phpbb_db_migration_data_30x_3_0_8_rc1 extends phpbb_db_migration array('config.update_if_equals', array(600, 'queue_interval', 60)), array('config.update_if_equals', array(50, 'email_package_size', 20)), - array('config.update', array('version', '3.0.8-rc1')), + array('config.update', array('version', '3.0.8-RC1')), ); } diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_9_rc1.php b/phpBB/phpbb/db/migration/data/30x/3_0_9_rc1.php index 4c345b429b..d3beda63b7 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_9_rc1.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_9_rc1.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_9_rc1 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.9-rc1', '>='); + return version_compare($this->config['version'], '3.0.9-RC1', '>='); } static public function depends_on() @@ -74,7 +74,7 @@ class phpbb_db_migration_data_30x_3_0_9_rc1 extends phpbb_db_migration array('custom', array(array(&$this, 'update_file_extension_group_names'))), array('custom', array(array(&$this, 'fix_firebird_qa_captcha'))), - array('config.update', array('version', '3.0.9-rc1')), + array('config.update', array('version', '3.0.9-RC1')), ); } diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_9_rc2.php b/phpBB/phpbb/db/migration/data/30x/3_0_9_rc2.php index c0e662aa45..beb8873da7 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_9_rc2.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_9_rc2.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_9_rc2 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.9-rc2', '>='); + return version_compare($this->config['version'], '3.0.9-RC2', '>='); } static public function depends_on() @@ -22,7 +22,7 @@ class phpbb_db_migration_data_30x_3_0_9_rc2 extends phpbb_db_migration public function update_data() { return array( - array('config.update', array('version', '3.0.9-rc2')), + array('config.update', array('version', '3.0.9-RC2')), ); } } diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_9_rc3.php b/phpBB/phpbb/db/migration/data/30x/3_0_9_rc3.php index d6d1f14b2e..56e04e7235 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_9_rc3.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_9_rc3.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_9_rc3 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.9-rc3', '>='); + return version_compare($this->config['version'], '3.0.9-RC3', '>='); } static public function depends_on() @@ -22,7 +22,7 @@ class phpbb_db_migration_data_30x_3_0_9_rc3 extends phpbb_db_migration public function update_data() { return array( - array('config.update', array('version', '3.0.9-rc3')), + array('config.update', array('version', '3.0.9-RC3')), ); } } diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_9_rc4.php b/phpBB/phpbb/db/migration/data/30x/3_0_9_rc4.php index e673249343..5be1124287 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_9_rc4.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_9_rc4.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_9_rc4 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.9-rc4', '>='); + return version_compare($this->config['version'], '3.0.9-RC4', '>='); } static public function depends_on() @@ -22,7 +22,7 @@ class phpbb_db_migration_data_30x_3_0_9_rc4 extends phpbb_db_migration public function update_data() { return array( - array('config.update', array('version', '3.0.9-rc4')), + array('config.update', array('version', '3.0.9-RC4')), ); } } -- cgit v1.2.1 From e667481c6c3d98808ac412531f562d68fad10ae5 Mon Sep 17 00:00:00 2001 From: Oliver Schramm Date: Fri, 2 Aug 2013 03:28:32 +0200 Subject: [ticket/11760] Use phpbb_version_compare() wrapper PHPBB3-11760 --- phpBB/phpbb/db/migration/data/30x/3_0_1.php | 2 +- phpBB/phpbb/db/migration/data/30x/3_0_10.php | 2 +- phpBB/phpbb/db/migration/data/30x/3_0_10_rc1.php | 2 +- phpBB/phpbb/db/migration/data/30x/3_0_10_rc2.php | 2 +- phpBB/phpbb/db/migration/data/30x/3_0_10_rc3.php | 2 +- phpBB/phpbb/db/migration/data/30x/3_0_11.php | 2 +- phpBB/phpbb/db/migration/data/30x/3_0_11_rc1.php | 2 +- phpBB/phpbb/db/migration/data/30x/3_0_11_rc2.php | 2 +- phpBB/phpbb/db/migration/data/30x/3_0_12_rc1.php | 2 +- phpBB/phpbb/db/migration/data/30x/3_0_1_rc1.php | 2 +- phpBB/phpbb/db/migration/data/30x/3_0_2.php | 2 +- phpBB/phpbb/db/migration/data/30x/3_0_2_rc1.php | 2 +- phpBB/phpbb/db/migration/data/30x/3_0_2_rc2.php | 2 +- phpBB/phpbb/db/migration/data/30x/3_0_3.php | 2 +- phpBB/phpbb/db/migration/data/30x/3_0_3_rc1.php | 2 +- phpBB/phpbb/db/migration/data/30x/3_0_4.php | 2 +- phpBB/phpbb/db/migration/data/30x/3_0_4_rc1.php | 2 +- phpBB/phpbb/db/migration/data/30x/3_0_5.php | 2 +- phpBB/phpbb/db/migration/data/30x/3_0_5_rc1.php | 2 +- phpBB/phpbb/db/migration/data/30x/3_0_5_rc1part2.php | 2 +- phpBB/phpbb/db/migration/data/30x/3_0_6.php | 2 +- phpBB/phpbb/db/migration/data/30x/3_0_6_rc1.php | 2 +- phpBB/phpbb/db/migration/data/30x/3_0_6_rc2.php | 2 +- phpBB/phpbb/db/migration/data/30x/3_0_6_rc3.php | 2 +- phpBB/phpbb/db/migration/data/30x/3_0_6_rc4.php | 2 +- phpBB/phpbb/db/migration/data/30x/3_0_7.php | 2 +- phpBB/phpbb/db/migration/data/30x/3_0_7_pl1.php | 2 +- phpBB/phpbb/db/migration/data/30x/3_0_7_rc1.php | 2 +- phpBB/phpbb/db/migration/data/30x/3_0_7_rc2.php | 2 +- phpBB/phpbb/db/migration/data/30x/3_0_8.php | 2 +- phpBB/phpbb/db/migration/data/30x/3_0_8_rc1.php | 2 +- phpBB/phpbb/db/migration/data/30x/3_0_9.php | 2 +- phpBB/phpbb/db/migration/data/30x/3_0_9_rc1.php | 2 +- phpBB/phpbb/db/migration/data/30x/3_0_9_rc2.php | 2 +- phpBB/phpbb/db/migration/data/30x/3_0_9_rc3.php | 2 +- phpBB/phpbb/db/migration/data/30x/3_0_9_rc4.php | 2 +- 36 files changed, 36 insertions(+), 36 deletions(-) (limited to 'phpBB') diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_1.php b/phpBB/phpbb/db/migration/data/30x/3_0_1.php index c996a0138a..c5b1681d96 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_1.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_1.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_1 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.1', '>='); + return phpbb_version_compare($this->config['version'], '3.0.1', '>='); } static public function depends_on() diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_10.php b/phpBB/phpbb/db/migration/data/30x/3_0_10.php index 122f93d6b4..640fcbc16f 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_10.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_10.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_10 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.10', '>='); + return phpbb_version_compare($this->config['version'], '3.0.10', '>='); } static public function depends_on() diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_10_rc1.php b/phpBB/phpbb/db/migration/data/30x/3_0_10_rc1.php index 459b423736..e0aca09c3a 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_10_rc1.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_10_rc1.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_10_rc1 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.10-RC1', '>='); + return phpbb_version_compare($this->config['version'], '3.0.10-RC1', '>='); } static public function depends_on() diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_10_rc2.php b/phpBB/phpbb/db/migration/data/30x/3_0_10_rc2.php index 8d21cab45d..394e030acf 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_10_rc2.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_10_rc2.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_10_rc2 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.10-RC2', '>='); + return phpbb_version_compare($this->config['version'], '3.0.10-RC2', '>='); } static public function depends_on() diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_10_rc3.php b/phpBB/phpbb/db/migration/data/30x/3_0_10_rc3.php index 296c3c40af..92900e3aed 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_10_rc3.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_10_rc3.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_10_rc3 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.10-RC3', '>='); + return phpbb_version_compare($this->config['version'], '3.0.10-RC3', '>='); } static public function depends_on() diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_11.php b/phpBB/phpbb/db/migration/data/30x/3_0_11.php index e063c699cc..3be03cec40 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_11.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_11.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_11 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.11', '>='); + return phpbb_version_compare($this->config['version'], '3.0.11', '>='); } static public function depends_on() diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_11_rc1.php b/phpBB/phpbb/db/migration/data/30x/3_0_11_rc1.php index 3a3908258f..f7b0247fdb 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_11_rc1.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_11_rc1.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_11_rc1 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.11-RC1', '>='); + return phpbb_version_compare($this->config['version'], '3.0.11-RC1', '>='); } static public function depends_on() diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_11_rc2.php b/phpBB/phpbb/db/migration/data/30x/3_0_11_rc2.php index 4b1b5642ce..204aa314ac 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_11_rc2.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_11_rc2.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_11_rc2 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.11-RC2', '>='); + return phpbb_version_compare($this->config['version'], '3.0.11-RC2', '>='); } static public function depends_on() diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_12_rc1.php b/phpBB/phpbb/db/migration/data/30x/3_0_12_rc1.php index 97331e9bb2..28ee84469a 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_12_rc1.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_12_rc1.php @@ -13,7 +13,7 @@ class phpbb_db_migration_data_30x_3_0_12_rc1 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.12-RC1', '>='); + return phpbb_version_compare($this->config['version'], '3.0.12-RC1', '>='); } static public function depends_on() diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_1_rc1.php b/phpBB/phpbb/db/migration/data/30x/3_0_1_rc1.php index 761ed5d2ec..984b8fb37e 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_1_rc1.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_1_rc1.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_1_rc1 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.1-RC1', '>='); + return phpbb_version_compare($this->config['version'], '3.0.1-RC1', '>='); } public function update_schema() diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_2.php b/phpBB/phpbb/db/migration/data/30x/3_0_2.php index eed5acef82..6e11e5a145 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_2.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_2.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_2 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.2', '>='); + return phpbb_version_compare($this->config['version'], '3.0.2', '>='); } static public function depends_on() diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_2_rc1.php b/phpBB/phpbb/db/migration/data/30x/3_0_2_rc1.php index a84f3c2d92..9a25628f25 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_2_rc1.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_2_rc1.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_2_rc1 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.2-RC1', '>='); + return phpbb_version_compare($this->config['version'], '3.0.2-RC1', '>='); } static public function depends_on() diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_2_rc2.php b/phpBB/phpbb/db/migration/data/30x/3_0_2_rc2.php index 33bacc077f..6c37d6701b 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_2_rc2.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_2_rc2.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_2_rc2 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.2-RC2', '>='); + return phpbb_version_compare($this->config['version'], '3.0.2-RC2', '>='); } static public function depends_on() diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_3.php b/phpBB/phpbb/db/migration/data/30x/3_0_3.php index 8984cf7b76..11fd2a2e80 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_3.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_3.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_3 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.3', '>='); + return phpbb_version_compare($this->config['version'], '3.0.3', '>='); } static public function depends_on() diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_3_rc1.php b/phpBB/phpbb/db/migration/data/30x/3_0_3_rc1.php index 69433f386e..cbeb00499a 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_3_rc1.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_3_rc1.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_3_rc1 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.3-RC1', '>='); + return phpbb_version_compare($this->config['version'], '3.0.3-RC1', '>='); } static public function depends_on() diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_4.php b/phpBB/phpbb/db/migration/data/30x/3_0_4.php index 9a0c132e78..4375a96dac 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_4.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_4.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_4 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.4', '>='); + return phpbb_version_compare($this->config['version'], '3.0.4', '>='); } static public function depends_on() diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_4_rc1.php b/phpBB/phpbb/db/migration/data/30x/3_0_4_rc1.php index e45bd3eeee..73334dcc6f 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_4_rc1.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_4_rc1.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_4_rc1 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.4-RC1', '>='); + return phpbb_version_compare($this->config['version'], '3.0.4-RC1', '>='); } static public function depends_on() diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_5.php b/phpBB/phpbb/db/migration/data/30x/3_0_5.php index 16d2dee457..2700274f35 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_5.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_5.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_5 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.5', '>='); + return phpbb_version_compare($this->config['version'], '3.0.5', '>='); } static public function depends_on() diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_5_rc1.php b/phpBB/phpbb/db/migration/data/30x/3_0_5_rc1.php index 62ae7bff1a..90c6b3b46a 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_5_rc1.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_5_rc1.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_5_rc1 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.5-RC1', '>='); + return phpbb_version_compare($this->config['version'], '3.0.5-RC1', '>='); } static public function depends_on() diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_5_rc1part2.php b/phpBB/phpbb/db/migration/data/30x/3_0_5_rc1part2.php index d72176489b..2d1e5cfed8 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_5_rc1part2.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_5_rc1part2.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_5_rc1part2 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.5-RC1', '>='); + return phpbb_version_compare($this->config['version'], '3.0.5-RC1', '>='); } static public function depends_on() diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_6.php b/phpBB/phpbb/db/migration/data/30x/3_0_6.php index bb651dc7cd..1877b0c5a1 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_6.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_6.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_6 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.6', '>='); + return phpbb_version_compare($this->config['version'], '3.0.6', '>='); } static public function depends_on() diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_6_rc1.php b/phpBB/phpbb/db/migration/data/30x/3_0_6_rc1.php index 25f4995b0e..3e2a9544c7 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_6_rc1.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_6_rc1.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_6_rc1 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.6-RC1', '>='); + return phpbb_version_compare($this->config['version'], '3.0.6-RC1', '>='); } static public function depends_on() diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_6_rc2.php b/phpBB/phpbb/db/migration/data/30x/3_0_6_rc2.php index d5d14ab05d..439e25b100 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_6_rc2.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_6_rc2.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_6_rc2 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.6-RC2', '>='); + return phpbb_version_compare($this->config['version'], '3.0.6-RC2', '>='); } static public function depends_on() diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_6_rc3.php b/phpBB/phpbb/db/migration/data/30x/3_0_6_rc3.php index f3f1fb42f4..77b62d7fc7 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_6_rc3.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_6_rc3.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_6_rc3 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.6-RC3', '>='); + return phpbb_version_compare($this->config['version'], '3.0.6-RC3', '>='); } static public function depends_on() diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_6_rc4.php b/phpBB/phpbb/db/migration/data/30x/3_0_6_rc4.php index 6138ef351d..61a31d09e6 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_6_rc4.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_6_rc4.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_6_rc4 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.6-RC4', '>='); + return phpbb_version_compare($this->config['version'], '3.0.6-RC4', '>='); } static public function depends_on() diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_7.php b/phpBB/phpbb/db/migration/data/30x/3_0_7.php index 9ff2e9e4ab..3eb1caddbc 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_7.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_7.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_7 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.7', '>='); + return phpbb_version_compare($this->config['version'], '3.0.7', '>='); } static public function depends_on() diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_7_pl1.php b/phpBB/phpbb/db/migration/data/30x/3_0_7_pl1.php index c9cc9d19ac..c7b5c584ac 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_7_pl1.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_7_pl1.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_7_pl1 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.7-pl1', '>='); + return phpbb_version_compare($this->config['version'], '3.0.7-pl1', '>='); } static public function depends_on() diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_7_rc1.php b/phpBB/phpbb/db/migration/data/30x/3_0_7_rc1.php index be8f9045f4..e0fd313834 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_7_rc1.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_7_rc1.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_7_rc1 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.7-RC1', '>='); + return phpbb_version_compare($this->config['version'], '3.0.7-RC1', '>='); } static public function depends_on() diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_7_rc2.php b/phpBB/phpbb/db/migration/data/30x/3_0_7_rc2.php index 0e43229f13..f4f3327385 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_7_rc2.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_7_rc2.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_7_rc2 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.7-RC2', '>='); + return phpbb_version_compare($this->config['version'], '3.0.7-RC2', '>='); } static public function depends_on() diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_8.php b/phpBB/phpbb/db/migration/data/30x/3_0_8.php index 8998ef9627..77771a9acd 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_8.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_8.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_8 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.8', '>='); + return phpbb_version_compare($this->config['version'], '3.0.8', '>='); } static public function depends_on() diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_8_rc1.php b/phpBB/phpbb/db/migration/data/30x/3_0_8_rc1.php index ff7824fa3b..c534cabb6c 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_8_rc1.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_8_rc1.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_8_rc1 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.8-RC1', '>='); + return phpbb_version_compare($this->config['version'], '3.0.8-RC1', '>='); } static public function depends_on() diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_9.php b/phpBB/phpbb/db/migration/data/30x/3_0_9.php index d5269ea6f0..6a38793269 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_9.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_9.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_9 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.9', '>='); + return phpbb_version_compare($this->config['version'], '3.0.9', '>='); } static public function depends_on() diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_9_rc1.php b/phpBB/phpbb/db/migration/data/30x/3_0_9_rc1.php index d3beda63b7..81c67550bd 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_9_rc1.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_9_rc1.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_9_rc1 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.9-RC1', '>='); + return phpbb_version_compare($this->config['version'], '3.0.9-RC1', '>='); } static public function depends_on() diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_9_rc2.php b/phpBB/phpbb/db/migration/data/30x/3_0_9_rc2.php index beb8873da7..1531f408b7 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_9_rc2.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_9_rc2.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_9_rc2 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.9-RC2', '>='); + return phpbb_version_compare($this->config['version'], '3.0.9-RC2', '>='); } static public function depends_on() diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_9_rc3.php b/phpBB/phpbb/db/migration/data/30x/3_0_9_rc3.php index 56e04e7235..851680b093 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_9_rc3.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_9_rc3.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_9_rc3 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.9-RC3', '>='); + return phpbb_version_compare($this->config['version'], '3.0.9-RC3', '>='); } static public function depends_on() diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_9_rc4.php b/phpBB/phpbb/db/migration/data/30x/3_0_9_rc4.php index 5be1124287..879538c341 100644 --- a/phpBB/phpbb/db/migration/data/30x/3_0_9_rc4.php +++ b/phpBB/phpbb/db/migration/data/30x/3_0_9_rc4.php @@ -11,7 +11,7 @@ class phpbb_db_migration_data_30x_3_0_9_rc4 extends phpbb_db_migration { public function effectively_installed() { - return version_compare($this->config['version'], '3.0.9-RC4', '>='); + return phpbb_version_compare($this->config['version'], '3.0.9-RC4', '>='); } static public function depends_on() -- cgit v1.2.1 From d7e048da10d17beeda85e67bc6fcf8649716441a Mon Sep 17 00:00:00 2001 From: rechosen Date: Sun, 14 Jul 2013 21:30:14 +0200 Subject: [ticket/9550] Add template event memberlist_view_user_statistics_before Adds a template event required for the karma extension. It allows adding entries to the user statistics part of any user profile. Explanation from http://area51.phpbb.com/phpBB/viewtopic.php?f=111&t=44379: Used by the karma extension to add a karma score indication that allows other users to see how respected this user is. Prepend because karma score is often a more meaningful statistic than the bottom statistics (like "Most active topic"), and should therefore be more prominent. PHPBB3-9550 --- phpBB/styles/prosilver/template/memberlist_view.html | 1 + phpBB/styles/subsilver2/template/memberlist_view.html | 1 + 2 files changed, 2 insertions(+) (limited to 'phpBB') diff --git a/phpBB/styles/prosilver/template/memberlist_view.html b/phpBB/styles/prosilver/template/memberlist_view.html index 57cfcb86d9..b339e07d37 100644 --- a/phpBB/styles/prosilver/template/memberlist_view.html +++ b/phpBB/styles/prosilver/template/memberlist_view.html @@ -77,6 +77,7 @@

    {L_USER_FORUM}

    +
    {L_JOINED}{L_COLON}
    {JOINED}
    {L_VISITED}{L_COLON}
    {VISITED}
    diff --git a/phpBB/styles/subsilver2/template/memberlist_view.html b/phpBB/styles/subsilver2/template/memberlist_view.html index 464369a7a8..e097e09565 100644 --- a/phpBB/styles/subsilver2/template/memberlist_view.html +++ b/phpBB/styles/subsilver2/template/memberlist_view.html @@ -66,6 +66,7 @@
    -- cgit v1.2.1 From ef7861bffc557e5f979ec91704d4a4da398484bc Mon Sep 17 00:00:00 2001 From: rechosen Date: Sun, 14 Jul 2013 22:06:41 +0200 Subject: [ticket/9550] Add template event viewtopic_body_post_buttons_after Adds a template event required for the karma extension. It allows adding post buttons to posts (next to the quote and edit buttons). Explanation from http://area51.phpbb.com/phpBB/viewtopic.php?f=111&t=44379: Used by the karma extension to add thumbs up/down icons that allow users to give karma on posts. PHPBB3-9550 --- phpBB/styles/prosilver/template/viewtopic_body.html | 1 + phpBB/styles/subsilver2/template/viewtopic_body.html | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'phpBB') diff --git a/phpBB/styles/prosilver/template/viewtopic_body.html b/phpBB/styles/prosilver/template/viewtopic_body.html index a8bac42842..54f0b27c4d 100644 --- a/phpBB/styles/prosilver/template/viewtopic_body.html +++ b/phpBB/styles/prosilver/template/viewtopic_body.html @@ -142,6 +142,7 @@
  • {L_WARN_USER}
  • {L_INFORMATION}
  • {L_REPLY_WITH_QUOTE}
  • + diff --git a/phpBB/styles/subsilver2/template/viewtopic_body.html b/phpBB/styles/subsilver2/template/viewtopic_body.html index 299b8219eb..9398953532 100644 --- a/phpBB/styles/subsilver2/template/viewtopic_body.html +++ b/phpBB/styles/subsilver2/template/viewtopic_body.html @@ -315,7 +315,7 @@
    - + -- cgit v1.2.1 From fd8ab9255981f9e613a0f0419c3115e4c470c5a7 Mon Sep 17 00:00:00 2001 From: rechosen Date: Sun, 14 Jul 2013 22:19:37 +0200 Subject: [ticket/9550] Add template event viewtopic_body_post_buttons_before Adds the prepend counterpart of a template event required for the karma extension. It allows adding post buttons to posts (next to the quote and edit buttons). Explanation from http://area51.phpbb.com/phpBB/viewtopic.php?f=111&t=44379: Per suggestion of nickvergessen, add a counterpart for every append or prepend event. PHPBB3-9550 --- phpBB/styles/prosilver/template/viewtopic_body.html | 1 + phpBB/styles/subsilver2/template/viewtopic_body.html | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'phpBB') diff --git a/phpBB/styles/prosilver/template/viewtopic_body.html b/phpBB/styles/prosilver/template/viewtopic_body.html index 54f0b27c4d..bc8a71c9bf 100644 --- a/phpBB/styles/prosilver/template/viewtopic_body.html +++ b/phpBB/styles/prosilver/template/viewtopic_body.html @@ -136,6 +136,7 @@
      +
    • {L_EDIT_POST}
    • {L_DELETE_POST}
    • {L_REPORT_POST}
    • diff --git a/phpBB/styles/subsilver2/template/viewtopic_body.html b/phpBB/styles/subsilver2/template/viewtopic_body.html index 9398953532..38847d0805 100644 --- a/phpBB/styles/subsilver2/template/viewtopic_body.html +++ b/phpBB/styles/subsilver2/template/viewtopic_body.html @@ -315,7 +315,7 @@
    - + -- cgit v1.2.1 From c049c8d6f0aea53e39de64c82a72ca4a724cb17d Mon Sep 17 00:00:00 2001 From: rechosen Date: Sun, 14 Jul 2013 22:25:49 +0200 Subject: [ticket/9550] Add template event viewtopic_body_postrow_custom_fields_before Adds a template event required for the karma extension. It allows adding data before the custom fields, under the username and avatar next to every post. Explanation from http://area51.phpbb.com/phpBB/viewtopic.php?f=111&t=44379: Used by the karma extension to add a karma score indication that allows users to see how respected the post author is. PHPBB3-9550 --- phpBB/styles/prosilver/template/viewtopic_body.html | 1 + phpBB/styles/subsilver2/template/viewtopic_body.html | 1 + 2 files changed, 2 insertions(+) (limited to 'phpBB') diff --git a/phpBB/styles/prosilver/template/viewtopic_body.html b/phpBB/styles/prosilver/template/viewtopic_body.html index bc8a71c9bf..3260ae8b92 100644 --- a/phpBB/styles/prosilver/template/viewtopic_body.html +++ b/phpBB/styles/prosilver/template/viewtopic_body.html @@ -239,6 +239,7 @@
    {postrow.PROFILE_FIELD1_NAME}{L_COLON} {postrow.PROFILE_FIELD1_VALUE}
    +
    {postrow.custom_fields.PROFILE_FIELD_NAME}{L_COLON} {postrow.custom_fields.PROFILE_FIELD_VALUE}
    diff --git a/phpBB/styles/subsilver2/template/viewtopic_body.html b/phpBB/styles/subsilver2/template/viewtopic_body.html index 38847d0805..787f67275b 100644 --- a/phpBB/styles/subsilver2/template/viewtopic_body.html +++ b/phpBB/styles/subsilver2/template/viewtopic_body.html @@ -208,6 +208,7 @@
    {postrow.PROFILE_FIELD1_NAME}{L_COLON} {postrow.PROFILE_FIELD1_VALUE} +
    {postrow.custom_fields.PROFILE_FIELD_NAME}{L_COLON} {postrow.custom_fields.PROFILE_FIELD_VALUE} -- cgit v1.2.1 From 84689680143f6e2b02dfb41d019419dce91a47ec Mon Sep 17 00:00:00 2001 From: rechosen Date: Sun, 14 Jul 2013 22:33:45 +0200 Subject: [ticket/9550] Add template event viewtopic_body_postrow_custom_fields_after Adds the append counterpart of a template event required for the karma extension. It allows adding data after the custom fields under the username and avatar next to every post. Explanation from http://area51.phpbb.com/phpBB/viewtopic.php?f=111&t=44379: Per suggestion of nickvergessen, add a counterpart for every append or prepend event. PHPBB3-9550 --- phpBB/styles/prosilver/template/viewtopic_body.html | 1 + phpBB/styles/subsilver2/template/viewtopic_body.html | 1 + 2 files changed, 2 insertions(+) (limited to 'phpBB') diff --git a/phpBB/styles/prosilver/template/viewtopic_body.html b/phpBB/styles/prosilver/template/viewtopic_body.html index 3260ae8b92..c8a99b18e4 100644 --- a/phpBB/styles/prosilver/template/viewtopic_body.html +++ b/phpBB/styles/prosilver/template/viewtopic_body.html @@ -243,6 +243,7 @@
    {postrow.custom_fields.PROFILE_FIELD_NAME}{L_COLON} {postrow.custom_fields.PROFILE_FIELD_VALUE}
    + diff --git a/phpBB/styles/subsilver2/template/viewtopic_body.html b/phpBB/styles/subsilver2/template/viewtopic_body.html index 787f67275b..51ab702b51 100644 --- a/phpBB/styles/subsilver2/template/viewtopic_body.html +++ b/phpBB/styles/subsilver2/template/viewtopic_body.html @@ -212,6 +212,7 @@
    {postrow.custom_fields.PROFILE_FIELD_NAME}{L_COLON} {postrow.custom_fields.PROFILE_FIELD_VALUE} + -- cgit v1.2.1 From 2f1635116cbaaee3645044022f883f075f5ded69 Mon Sep 17 00:00:00 2001 From: rechosen Date: Sun, 14 Jul 2013 23:31:41 +0200 Subject: [ticket/9550] Add template event ucp_pm_viewmessage_custom_fields_before Adds a template event required for the karma extension. It allows adding data before the custom fields under the username and avatar next to every private message in prosilver. Explanation from http://area51.phpbb.com/phpBB/viewtopic.php?f=111&t=44379: Used by the karma extension to add a karma score indication that allows users to see how respected the pm sender is. PHPBB3-9550 --- phpBB/styles/prosilver/template/ucp_pm_viewmessage.html | 1 + 1 file changed, 1 insertion(+) (limited to 'phpBB') diff --git a/phpBB/styles/prosilver/template/ucp_pm_viewmessage.html b/phpBB/styles/prosilver/template/ucp_pm_viewmessage.html index 22149c8b80..accea4d3dd 100644 --- a/phpBB/styles/prosilver/template/ucp_pm_viewmessage.html +++ b/phpBB/styles/prosilver/template/ucp_pm_viewmessage.html @@ -85,6 +85,7 @@
    {L_JOINED}{L_COLON} {AUTHOR_JOINED}
    {L_LOCATION}{L_COLON} {AUTHOR_FROM}
    +
    {custom_fields.PROFILE_FIELD_NAME}{L_COLON} {custom_fields.PROFILE_FIELD_VALUE}
    -- cgit v1.2.1 From cd0b1be2082f11a80b42f7f25841d0e432ff5912 Mon Sep 17 00:00:00 2001 From: rechosen Date: Sun, 14 Jul 2013 23:36:00 +0200 Subject: [ticket/9550] Add template event ucp_pm_viewmessage_custom_fields_after Adds the append counterpart of a template event required for the karma extension. It allows adding data after the custom fields under the username and avatar next to every private message in prosilver. Explanation from http://area51.phpbb.com/phpBB/viewtopic.php?f=111&t=44379: Per suggestion of nickvergessen, add a counterpart for every append or prepend event. PHPBB3-9550 --- phpBB/styles/prosilver/template/ucp_pm_viewmessage.html | 1 + 1 file changed, 1 insertion(+) (limited to 'phpBB') diff --git a/phpBB/styles/prosilver/template/ucp_pm_viewmessage.html b/phpBB/styles/prosilver/template/ucp_pm_viewmessage.html index accea4d3dd..4f2531d3a6 100644 --- a/phpBB/styles/prosilver/template/ucp_pm_viewmessage.html +++ b/phpBB/styles/prosilver/template/ucp_pm_viewmessage.html @@ -89,6 +89,7 @@
    {custom_fields.PROFILE_FIELD_NAME}{L_COLON} {custom_fields.PROFILE_FIELD_VALUE}
    + -- cgit v1.2.1 From ee251fe45b6fb27900df91a3909e28aa8c785543 Mon Sep 17 00:00:00 2001 From: rechosen Date: Wed, 17 Jul 2013 13:44:27 +0200 Subject: [ticket/9550] Add template event memberlist_body_username_append Adds a template event required for the karma extension. It allows adding information after every username in the memberlist. Explanation from http://area51.phpbb.com/phpBB/viewtopic.php?f=111&t=44379: Used by the karma extension to add a karma score indication that allows other users to see how respected every listed user is. PHPBB3-9550 --- phpBB/styles/prosilver/template/memberlist_body.html | 2 +- phpBB/styles/subsilver2/template/memberlist_body.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'phpBB') diff --git a/phpBB/styles/prosilver/template/memberlist_body.html b/phpBB/styles/prosilver/template/memberlist_body.html index 07a7e2e182..4c5c576eb1 100644 --- a/phpBB/styles/prosilver/template/memberlist_body.html +++ b/phpBB/styles/prosilver/template/memberlist_body.html @@ -109,7 +109,7 @@
    - + diff --git a/phpBB/styles/subsilver2/template/memberlist_body.html b/phpBB/styles/subsilver2/template/memberlist_body.html index 09336fb8a3..8fd7451b55 100644 --- a/phpBB/styles/subsilver2/template/memberlist_body.html +++ b/phpBB/styles/subsilver2/template/memberlist_body.html @@ -66,7 +66,7 @@ - + -- cgit v1.2.1 From 075f491cb868119bf7de2f3dc6e0f0f79db7156e Mon Sep 17 00:00:00 2001 From: rechosen Date: Wed, 17 Jul 2013 13:49:12 +0200 Subject: [ticket/9550] Add template event memberlist_body_username_prepend Adds the prepend counterpart of a template event required for the karma extension. It allows adding information before every username in the memberlist. Explanation from http://area51.phpbb.com/phpBB/viewtopic.php?f=111&t=44379: Per suggestion of nickvergessen, add a counterpart for every append or prepend event. PHPBB3-9550 --- phpBB/styles/prosilver/template/memberlist_body.html | 2 +- phpBB/styles/subsilver2/template/memberlist_body.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'phpBB') diff --git a/phpBB/styles/prosilver/template/memberlist_body.html b/phpBB/styles/prosilver/template/memberlist_body.html index 4c5c576eb1..459c3f6bc6 100644 --- a/phpBB/styles/prosilver/template/memberlist_body.html +++ b/phpBB/styles/prosilver/template/memberlist_body.html @@ -109,7 +109,7 @@ - + diff --git a/phpBB/styles/subsilver2/template/memberlist_body.html b/phpBB/styles/subsilver2/template/memberlist_body.html index 8fd7451b55..7c4d301de7 100644 --- a/phpBB/styles/subsilver2/template/memberlist_body.html +++ b/phpBB/styles/subsilver2/template/memberlist_body.html @@ -66,7 +66,7 @@ - + -- cgit v1.2.1 From 8c565ea1a642ab7b72d22eaf6c83236cef9444a1 Mon Sep 17 00:00:00 2001 From: rechosen Date: Wed, 17 Jul 2013 15:06:53 +0200 Subject: [ticket/9550] Add the new template events to phpBB/docs/events.md The newly added template events weren't listed and described yet in events.md. Fixed now. PHPBB3-9550 --- phpBB/docs/events.md | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) (limited to 'phpBB') diff --git a/phpBB/docs/events.md b/phpBB/docs/events.md index 855f238653..d037237b34 100644 --- a/phpBB/docs/events.md +++ b/phpBB/docs/events.md @@ -52,6 +52,38 @@ index_body_stat_blocks_before + styles/subsilver2/template/index_body.html * Purpose: Add new statistic blocks above the Who Is Online and Board Statistics blocks +memberlist_body_username_append +=== +* Locations: + + styles/prosilver/template/memberlist_body.html + + styles/subsilver2/template/memberlist_body.html +* Purpose: Add information after every username in the memberlist. Works in +all display modes (leader, group and normal memberlist). + +memberlist_body_username_prepend +=== +* Locations: + + styles/prosilver/template/memberlist_body.html + + styles/subsilver2/template/memberlist_body.html +* Purpose: Add information before every username in the memberlist. Works in +all display modes (leader, group and normal memberlist). + +memberlist_view_user_statistics_after +=== +* Locations: + + styles/prosilver/template/memberlist_view.html + + styles/subsilver2/template/memberlist_view.html +* Purpose: Add entries to the bottom of the user statistics part of any user +profile + +memberlist_view_user_statistics_before +=== +* Locations: + + styles/prosilver/template/memberlist_view.html + + styles/subsilver2/template/memberlist_view.html +* Purpose: Add entries to the top of the user statistics part of any user +profile + overall_footer_after === * Locations: @@ -132,6 +164,18 @@ topiclist_row_append + styles/subsilver2/template/viewforum_body.html * Purpose: Add content into topic rows (inside the elements containing topic titles) +ucp_pm_viewmessage_custom_fields_after +=== +* Location: styles/prosilver/template/ucp_pm_viewmessage.html +* Purpose: Add data after the custom fields, under the username and avatar +next to every private message in prosilver + +ucp_pm_viewmessage_custom_fields_before +=== +* Location: styles/prosilver/template/ucp_pm_viewmessage.html +* Purpose: Add data before the custom fields, under the username and avatar +next to every private message in prosilver + ucp_pm_viewmessage_print_head_append === * Location: styles/prosilver/template/ucp_pm_viewmessage_print.html @@ -151,6 +195,38 @@ viewtopic_body_footer_before and quick reply, directly before the jumpbox in Prosilver, breadcrumbs in Subsilver2. +viewtopic_body_post_buttons_after +=== +* Locations: + + styles/prosilver/template/viewtopic_body.html + + styles/subsilver2/template/viewtopic_body.html +* Purpose: Add post button to posts (next to edit, quote etc), at the end of +the list. + +viewtopic_body_post_buttons_before +=== +* Locations: + + styles/prosilver/template/viewtopic_body.html + + styles/subsilver2/template/viewtopic_body.html +* Purpose: Add post button to posts (next to edit, quote etc), at the start of +the list. + +viewtopic_body_postrow_custom_fields_after +=== +* Locations: + + styles/prosilver/template/viewtopic_body.html + + styles/subsilver2/template/viewtopic_body.html +* Purpose: Add data after the postrow custom fields, under the username and +avatar next to every post. + +viewtopic_body_postrow_custom_fields_before +=== +* Locations: + + styles/prosilver/template/viewtopic_body.html + + styles/subsilver2/template/viewtopic_body.html +* Purpose: Add data before the postrow custom fields, under the username and +avatar next to every post. + viewtopic_topic_title_prepend === * Locations: -- cgit v1.2.1 From f61910c3f83fd4de16ca946232bab1bd3c679341 Mon Sep 17 00:00:00 2001 From: rechosen Date: Fri, 2 Aug 2013 11:38:25 +0200 Subject: [ticket/9550] Improve template event descriptions in phpBB/docs/events.md Per suggestion of nickvergessen, stick to "before" and "after" in the template event descriptions instead of "at the top of" and "at the bottom of". PHPBB3-9550 --- phpBB/docs/events.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'phpBB') diff --git a/phpBB/docs/events.md b/phpBB/docs/events.md index d037237b34..b1e8c7ad05 100644 --- a/phpBB/docs/events.md +++ b/phpBB/docs/events.md @@ -73,16 +73,14 @@ memberlist_view_user_statistics_after * Locations: + styles/prosilver/template/memberlist_view.html + styles/subsilver2/template/memberlist_view.html -* Purpose: Add entries to the bottom of the user statistics part of any user -profile +* Purpose: Add entries after the user statistics part of any user profile memberlist_view_user_statistics_before === * Locations: + styles/prosilver/template/memberlist_view.html + styles/subsilver2/template/memberlist_view.html -* Purpose: Add entries to the top of the user statistics part of any user -profile +* Purpose: Add entries before the user statistics part of any user profile overall_footer_after === -- cgit v1.2.1 From 61fd61692b5c93eee560424717d272d84d710cd4 Mon Sep 17 00:00:00 2001 From: rechosen Date: Fri, 2 Aug 2013 11:44:07 +0200 Subject: [ticket/9550] Improve template event descriptions in phpBB/docs/events.md Update the custom_fields template events descriptions according to nickvergessen's suggestions. PHPBB3-9550 --- phpBB/docs/events.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'phpBB') diff --git a/phpBB/docs/events.md b/phpBB/docs/events.md index b1e8c7ad05..af6e6bdb1c 100644 --- a/phpBB/docs/events.md +++ b/phpBB/docs/events.md @@ -165,14 +165,14 @@ topiclist_row_append ucp_pm_viewmessage_custom_fields_after === * Location: styles/prosilver/template/ucp_pm_viewmessage.html -* Purpose: Add data after the custom fields, under the username and avatar -next to every private message in prosilver +* Purpose: Add data after the custom fields on the user profile when viewing +a private message ucp_pm_viewmessage_custom_fields_before === * Location: styles/prosilver/template/ucp_pm_viewmessage.html -* Purpose: Add data before the custom fields, under the username and avatar -next to every private message in prosilver +* Purpose: Add data before the custom fields on the user profile when viewing +a private message ucp_pm_viewmessage_print_head_append === @@ -214,16 +214,16 @@ viewtopic_body_postrow_custom_fields_after * Locations: + styles/prosilver/template/viewtopic_body.html + styles/subsilver2/template/viewtopic_body.html -* Purpose: Add data after the postrow custom fields, under the username and -avatar next to every post. +* Purpose: Add data after the custom fields on the user profile when viewing +a post viewtopic_body_postrow_custom_fields_before === * Locations: + styles/prosilver/template/viewtopic_body.html + styles/subsilver2/template/viewtopic_body.html -* Purpose: Add data before the postrow custom fields, under the username and -avatar next to every post. +* Purpose: Add data before the custom fields on the user profile when viewing +a post viewtopic_topic_title_prepend === -- cgit v1.2.1 From 351abf38830ddb8d6466f39ec6a173ce688b0206 Mon Sep 17 00:00:00 2001 From: rechosen Date: Fri, 2 Aug 2013 14:07:49 +0200 Subject: [ticket/9550] Break up a long ugly line in subsilver2 viewtopic_body.html Per request of nickvergessen, break up the long post buttons line in viewtopic_body.html of subsilver2 into nicely indented parent and child elements. Keep the whitespace in such a way that browsers display the buttons pixel-perfectly equal to the old code. In the process, move the viewtopic_body_post_buttons_before event to a more logical (though possibly less intuitive) place (only in subsilver2). PHPBB3-9550 --- phpBB/styles/subsilver2/template/viewtopic_body.html | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) (limited to 'phpBB') diff --git a/phpBB/styles/subsilver2/template/viewtopic_body.html b/phpBB/styles/subsilver2/template/viewtopic_body.html index 51ab702b51..26e74b8c38 100644 --- a/phpBB/styles/subsilver2/template/viewtopic_body.html +++ b/phpBB/styles/subsilver2/template/viewtopic_body.html @@ -317,7 +317,21 @@ - + -- cgit v1.2.1 From 8a02db317ef3c2d3c4e3dcfc5f8b85397f7ebb4a Mon Sep 17 00:00:00 2001 From: s9e Date: Sat, 3 Aug 2013 12:20:52 +0200 Subject: [ticket/11762] Use the === operator to distinguish "0" from "" PHPBB3-11762 --- phpBB/includes/functions_content.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'phpBB') diff --git a/phpBB/includes/functions_content.php b/phpBB/includes/functions_content.php index b7650ecd6a..6213d2fd24 100644 --- a/phpBB/includes/functions_content.php +++ b/phpBB/includes/functions_content.php @@ -413,7 +413,7 @@ function generate_text_for_display($text, $uid, $bitfield, $flags) { static $bbcode; - if (!$text) + if ($text === '') { return ''; } @@ -459,7 +459,7 @@ function generate_text_for_storage(&$text, &$uid, &$bitfield, &$flags, $allow_bb $uid = $bitfield = ''; $flags = (($allow_bbcode) ? OPTION_FLAG_BBCODE : 0) + (($allow_smilies) ? OPTION_FLAG_SMILIES : 0) + (($allow_urls) ? OPTION_FLAG_LINKS : 0); - if (!$text) + if ($text === '') { return; } -- cgit v1.2.1 From 71ababe6fb1648c9cb286c8c84e939a36a35cbbf Mon Sep 17 00:00:00 2001 From: Oliver Schramm Date: Sat, 3 Aug 2013 17:30:28 +0200 Subject: [ticket/11763] Add missing variable when reporting a PM to avoid SQL Error PHPBB3-11763 --- phpBB/report.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'phpBB') diff --git a/phpBB/report.php b/phpBB/report.php index c9ca57ecbe..5081f90ad1 100644 --- a/phpBB/report.php +++ b/phpBB/report.php @@ -139,9 +139,10 @@ else $reported_post_text = $report_data['message_text']; $reported_post_bitfield = $report_data['bbcode_bitfield']; - $reported_post_enable_bbcode = $report_data['reported_post_enable_bbcode']; - $reported_post_enable_smilies = $report_data['reported_post_enable_smilies']; - $reported_post_enable_magic_url = $report_data['reported_post_enable_magic_url']; + $reported_post_uid = $report_data['bbcode_uid']; + $reported_post_enable_bbcode = $report_data['enable_bbcode']; + $reported_post_enable_smilies = $report_data['enable_smilies']; + $reported_post_enable_magic_url = $report_data['enable_magic_url']; } if ($config['enable_post_confirm'] && !$user->data['is_registered']) -- cgit v1.2.1 From 28a0a9e0b1cf77f1be64934f98e4c607d6098362 Mon Sep 17 00:00:00 2001 From: brunoais Date: Sat, 3 Aug 2013 21:46:06 +0100 Subject: [ticket/11639] Changing how censorship is handled. sub-task of ticket PHPBB3-11635: find and fix all bypasses of generate_text_for_* PHPBB3-11639 --- phpBB/includes/functions_posting.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'phpBB') diff --git a/phpBB/includes/functions_posting.php b/phpBB/includes/functions_posting.php index acf0cc874b..c528d36fef 100644 --- a/phpBB/includes/functions_posting.php +++ b/phpBB/includes/functions_posting.php @@ -1093,13 +1093,12 @@ function topic_review($topic_id, $forum_id, $mode = 'topic_review', $cur_post_id $poster_id = $row['user_id']; $post_subject = $row['post_subject']; - $message = censor_text($row['post_text']); $decoded_message = false; if ($show_quote_button && $auth->acl_get('f_reply', $forum_id)) { - $decoded_message = $message; + $decoded_message = censor_text($row['post_text']); decode_message($decoded_message, $row['bbcode_uid']); $decoded_message = bbcode_nl2br($decoded_message); @@ -1107,8 +1106,7 @@ function topic_review($topic_id, $forum_id, $mode = 'topic_review', $cur_post_id $parse_flags = ($row['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0); $parse_flags |= ($row['enable_smilies'] ? OPTION_FLAG_SMILIES : 0); - // Do not censor text because it has already been censored before - $message = generate_text_for_display($message, $row['bbcode_uid'], $row['bbcode_bitfield'], $parse_flags, false); + $message = generate_text_for_display($row['post_text'], $row['bbcode_uid'], $row['bbcode_bitfield'], $parse_flags, true); if (!empty($attachments[$row['post_id']])) { -- cgit v1.2.1 From 3713ff71eafa575fc78f788085803ec6488c8fcd Mon Sep 17 00:00:00 2001 From: Vjacheslav Trushkin Date: Tue, 6 Aug 2013 21:47:35 +0300 Subject: [ticket/11770] Fix class name for pm list PHPBB3-11770 --- phpBB/styles/prosilver/template/ucp_pm_viewfolder.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB') diff --git a/phpBB/styles/prosilver/template/ucp_pm_viewfolder.html b/phpBB/styles/prosilver/template/ucp_pm_viewfolder.html index c5078df268..9cbff64a6a 100644 --- a/phpBB/styles/prosilver/template/ucp_pm_viewfolder.html +++ b/phpBB/styles/prosilver/template/ucp_pm_viewfolder.html @@ -53,7 +53,7 @@ -
      +
      • -- cgit v1.2.1 From 2508439b02f5115e0557b2eee467e5ec5737d350 Mon Sep 17 00:00:00 2001 From: Andy Chase Date: Mon, 5 Aug 2013 10:35:39 -0700 Subject: [ticket/11761] Serve blank file locally in functional test Example.org no longer serves blank responses, failing functional tests. this patch creates a blank file and serve it locally during the test, instead of hitting the http://example.org servers kindly provided by IANA. PHPBB3-11761 --- phpBB/develop/blank.gif | 0 phpBB/develop/blank.jpg | 0 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 phpBB/develop/blank.gif create mode 100644 phpBB/develop/blank.jpg (limited to 'phpBB') diff --git a/phpBB/develop/blank.gif b/phpBB/develop/blank.gif new file mode 100644 index 0000000000..e69de29bb2 diff --git a/phpBB/develop/blank.jpg b/phpBB/develop/blank.jpg new file mode 100644 index 0000000000..e69de29bb2 -- cgit v1.2.1 From 91eccc708bb0ca4143ad670be6ecddef818b9316 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Thu, 8 Aug 2013 13:42:51 +0200 Subject: [ticket/11775] Fix error when moving the last post to another topic PHPBB3-11775 --- phpBB/includes/mcp/mcp_topic.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'phpBB') diff --git a/phpBB/includes/mcp/mcp_topic.php b/phpBB/includes/mcp/mcp_topic.php index 76985488b7..8e0e89e3da 100644 --- a/phpBB/includes/mcp/mcp_topic.php +++ b/phpBB/includes/mcp/mcp_topic.php @@ -668,10 +668,10 @@ function merge_posts($topic_id, $to_topic_id) } // If the topic no longer exist, we will update the topic watch table. - phpbb_update_rows_avoiding_duplicates_notify_status($db, TOPICS_WATCH_TABLE, 'topic_id', $topic_ids, $to_topic_id); + phpbb_update_rows_avoiding_duplicates_notify_status($db, TOPICS_WATCH_TABLE, 'topic_id', array($topic_id), $to_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); + phpbb_update_rows_avoiding_duplicates($db, BOOKMARKS_TABLE, 'topic_id', array($topic_id), $to_topic_id); } // Link to the new topic -- cgit v1.2.1 From 74559eb0d59d9b97d6769ef5d57a3b375a6b8507 Mon Sep 17 00:00:00 2001 From: Oliver Schramm Date: Thu, 8 Aug 2013 15:50:20 +0200 Subject: [ticket/11774] Fix constant to avoid PHP errors PHPBB3-11774 --- phpBB/includes/mcp/mcp_reports.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB') diff --git a/phpBB/includes/mcp/mcp_reports.php b/phpBB/includes/mcp/mcp_reports.php index 72400ce623..3f48c58073 100644 --- a/phpBB/includes/mcp/mcp_reports.php +++ b/phpBB/includes/mcp/mcp_reports.php @@ -187,7 +187,7 @@ class mcp_reports 'S_CLOSE_ACTION' => append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=reports&mode=report_details&f=' . $post_info['forum_id'] . '&p=' . $post_id), 'S_CAN_VIEWIP' => $auth->acl_get('m_info', $post_info['forum_id']), 'S_POST_REPORTED' => $post_info['post_reported'], - 'S_POST_UNAPPROVED' => ($post_info['post_visibility'] == POST_UNAPPROVED), + 'S_POST_UNAPPROVED' => ($post_info['post_visibility'] == ITEM_UNAPPROVED), 'S_POST_LOCKED' => $post_info['post_edit_locked'], 'S_USER_NOTES' => true, -- cgit v1.2.1 From 0aea5e48d8ad3505fc957d67131eece477d84947 Mon Sep 17 00:00:00 2001 From: Vjacheslav Trushkin Date: Sun, 11 Aug 2013 10:37:29 +0300 Subject: [ticket/11779] Fix unapproved messages class name PHPBB3-11779 --- phpBB/styles/prosilver/template/mcp_front.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'phpBB') diff --git a/phpBB/styles/prosilver/template/mcp_front.html b/phpBB/styles/prosilver/template/mcp_front.html index e88b7c62df..402cfe029a 100644 --- a/phpBB/styles/prosilver/template/mcp_front.html +++ b/phpBB/styles/prosilver/template/mcp_front.html @@ -13,7 +13,7 @@

        {L_UNAPPROVED_TOTAL}

        -
          +
          • {L_VIEW_DETAILS}
            @@ -21,7 +21,7 @@
          -
            +
            • -- cgit v1.2.1 From fe3b57a1416fbc70779aebe6ff15fd6f1733f269 Mon Sep 17 00:00:00 2001 From: Vjacheslav Trushkin Date: Sun, 11 Aug 2013 10:51:16 +0300 Subject: [ticket/11780] Remove unused images PHPBB3-11780 --- phpBB/styles/prosilver/theme/en/button_pm_forward.gif | Bin 2168 -> 0 bytes phpBB/styles/prosilver/theme/en/button_pm_new.gif | Bin 2005 -> 0 bytes phpBB/styles/prosilver/theme/en/button_pm_reply.gif | Bin 2126 -> 0 bytes phpBB/styles/prosilver/theme/en/button_topic_locked.gif | Bin 1923 -> 0 bytes phpBB/styles/prosilver/theme/en/button_topic_new.gif | Bin 2737 -> 0 bytes phpBB/styles/prosilver/theme/en/button_topic_reply.gif | Bin 2135 -> 0 bytes 6 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 phpBB/styles/prosilver/theme/en/button_pm_forward.gif delete mode 100644 phpBB/styles/prosilver/theme/en/button_pm_new.gif delete mode 100644 phpBB/styles/prosilver/theme/en/button_pm_reply.gif delete mode 100644 phpBB/styles/prosilver/theme/en/button_topic_locked.gif delete mode 100644 phpBB/styles/prosilver/theme/en/button_topic_new.gif delete mode 100644 phpBB/styles/prosilver/theme/en/button_topic_reply.gif (limited to 'phpBB') diff --git a/phpBB/styles/prosilver/theme/en/button_pm_forward.gif b/phpBB/styles/prosilver/theme/en/button_pm_forward.gif deleted file mode 100644 index 3384df34be..0000000000 Binary files a/phpBB/styles/prosilver/theme/en/button_pm_forward.gif and /dev/null differ diff --git a/phpBB/styles/prosilver/theme/en/button_pm_new.gif b/phpBB/styles/prosilver/theme/en/button_pm_new.gif deleted file mode 100644 index cc0381c6b2..0000000000 Binary files a/phpBB/styles/prosilver/theme/en/button_pm_new.gif and /dev/null differ diff --git a/phpBB/styles/prosilver/theme/en/button_pm_reply.gif b/phpBB/styles/prosilver/theme/en/button_pm_reply.gif deleted file mode 100644 index 3275b06d52..0000000000 Binary files a/phpBB/styles/prosilver/theme/en/button_pm_reply.gif and /dev/null differ diff --git a/phpBB/styles/prosilver/theme/en/button_topic_locked.gif b/phpBB/styles/prosilver/theme/en/button_topic_locked.gif deleted file mode 100644 index b08918a24f..0000000000 Binary files a/phpBB/styles/prosilver/theme/en/button_topic_locked.gif and /dev/null differ diff --git a/phpBB/styles/prosilver/theme/en/button_topic_new.gif b/phpBB/styles/prosilver/theme/en/button_topic_new.gif deleted file mode 100644 index 5b7b1e0e60..0000000000 Binary files a/phpBB/styles/prosilver/theme/en/button_topic_new.gif and /dev/null differ diff --git a/phpBB/styles/prosilver/theme/en/button_topic_reply.gif b/phpBB/styles/prosilver/theme/en/button_topic_reply.gif deleted file mode 100644 index e900c80c70..0000000000 Binary files a/phpBB/styles/prosilver/theme/en/button_topic_reply.gif and /dev/null differ -- cgit v1.2.1 From fe97611eacb913a93b8c604d2ceb97e3fd42d744 Mon Sep 17 00:00:00 2001 From: Vjacheslav Trushkin Date: Sun, 11 Aug 2013 10:51:37 +0300 Subject: [ticket/11780] Remove references to unused images PHPBB3-11780 --- phpBB/styles/prosilver/theme/en/stylesheet.css | 30 -------------------------- phpBB/styles/prosilver/theme/imageset.css | 30 -------------------------- 2 files changed, 60 deletions(-) (limited to 'phpBB') diff --git a/phpBB/styles/prosilver/theme/en/stylesheet.css b/phpBB/styles/prosilver/theme/en/stylesheet.css index 1a3d0acb4b..82b7df0830 100644 --- a/phpBB/styles/prosilver/theme/en/stylesheet.css +++ b/phpBB/styles/prosilver/theme/en/stylesheet.css @@ -32,33 +32,3 @@ ul.profile-icons li.edit-icon { width: 42px; height: 20px; } padding-left: 58px; padding-top: 58px; } -.imageset.button_pm_forward { - background-image: url("./button_pm_forward.gif"); - padding-left: 96px; - padding-top: 25px; -} -.imageset.button_pm_new { - background-image: url("./button_pm_new.gif"); - padding-left: 84px; - padding-top: 25px; -} -.imageset.button_pm_reply { - background-image: url("./button_pm_reply.gif"); - padding-left: 96px; - padding-top: 25px; -} -.imageset.button_topic_locked { - background-image: url("./button_topic_locked.gif"); - padding-left: 88px; - padding-top: 25px; -} -.imageset.button_topic_new { - background-image: url("./button_topic_new.gif"); - padding-left: 96px; - padding-top: 25px; -} -.imageset.button_topic_reply { - background-image: url("./button_topic_reply.gif"); - padding-left: 96px; - padding-top: 25px; -} diff --git a/phpBB/styles/prosilver/theme/imageset.css b/phpBB/styles/prosilver/theme/imageset.css index 296c617f17..7aa19df06e 100644 --- a/phpBB/styles/prosilver/theme/imageset.css +++ b/phpBB/styles/prosilver/theme/imageset.css @@ -378,33 +378,3 @@ span.imageset { padding-left: 58px; padding-top: 58px; } -.imageset.button_pm_forward { - background-image: url("./en/button_pm_forward.gif"); - padding-left: 96px; - padding-top: 25px; -} -.imageset.button_pm_new { - background-image: url("./en/button_pm_new.gif"); - padding-left: 84px; - padding-top: 25px; -} -.imageset.button_pm_reply { - background-image: url("./en/button_pm_reply.gif"); - padding-left: 96px; - padding-top: 25px; -} -.imageset.button_topic_locked { - background-image: url("./en/button_topic_locked.gif"); - padding-left: 88px; - padding-top: 25px; -} -.imageset.button_topic_new { - background-image: url("./en/button_topic_new.gif"); - padding-left: 96px; - padding-top: 25px; -} -.imageset.button_topic_reply { - background-image: url("./en/button_topic_reply.gif"); - padding-left: 96px; - padding-top: 25px; -} -- cgit v1.2.1 From a0206a61bcc174d04f80a6e172f37b25f5b84694 Mon Sep 17 00:00:00 2001 From: Vjacheslav Trushkin Date: Sun, 11 Aug 2013 11:31:02 +0300 Subject: [ticket/11781] Include func update_post_information() Include functions_posting before using functions defined in that file PHPBB3-11781 --- phpBB/phpbb/content_visibility.php | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'phpBB') diff --git a/phpBB/phpbb/content_visibility.php b/phpBB/phpbb/content_visibility.php index 4ad5f6793e..fb8ece0e8c 100644 --- a/phpBB/phpbb/content_visibility.php +++ b/phpBB/phpbb/content_visibility.php @@ -360,6 +360,11 @@ class phpbb_content_visibility // Sync the first/last topic information if needed if (!$is_starter && $is_latest) { + if (!function_exists('update_post_information')) + { + include($this->phpbb_root_path . 'includes/functions_posting.' . $this->php_ext); + } + // update_post_information can only update the last post info ... if ($topic_id) { -- cgit v1.2.1 From 49824a0fd33df6b9da4e413ccd189379fb7f728b Mon Sep 17 00:00:00 2001 From: rechosen Date: Thu, 8 Aug 2013 11:31:06 +0200 Subject: [ticket/11777] Add subdirectory 'events/' to the template event search path Makes the twig template engine look in the events/ subdirectory instead of the main styles/[style]/template/ directory for extension template events. Note that it does _not_ look recursively! PHPBB3-11777 --- phpBB/phpbb/template/twig/node/event.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'phpBB') diff --git a/phpBB/phpbb/template/twig/node/event.php b/phpBB/phpbb/template/twig/node/event.php index 971dea14fa..30a9963a77 100644 --- a/phpBB/phpbb/template/twig/node/event.php +++ b/phpBB/phpbb/template/twig/node/event.php @@ -18,6 +18,11 @@ if (!defined('IN_PHPBB')) class phpbb_template_twig_node_event extends Twig_Node { + /** + * The subdirectory in which all template event files must be placed + */ + const TEMPLATE_EVENTS_SUBDIRECTORY = 'events/'; + /** @var Twig_Environment */ protected $environment; @@ -50,19 +55,19 @@ class phpbb_template_twig_node_event extends Twig_Node // slower, but makes developing extensions easier (no need to // purge the cache when a new event template file is added) $compiler - ->write("if (\$this->env->getLoader()->exists('@{$ext_namespace}/{$location}.html')) {\n") + ->write("if (\$this->env->getLoader()->exists('@{$ext_namespace}/" . self::TEMPLATE_EVENTS_SUBDIRECTORY . "{$location}.html')) {\n") ->indent() ; } - if (defined('DEBUG') || $this->environment->getLoader()->exists('@' . $ext_namespace . '/' . $location . '.html')) + if (defined('DEBUG') || $this->environment->getLoader()->exists('@' . $ext_namespace . '/' . self::TEMPLATE_EVENTS_SUBDIRECTORY . $location . '.html')) { $compiler ->write("\$previous_look_up_order = \$this->env->getNamespaceLookUpOrder();\n") // We set the namespace lookup order to be this extension first, then the main path ->write("\$this->env->setNamespaceLookUpOrder(array('{$ext_namespace}', '__main__'));\n") - ->write("\$this->env->loadTemplate('@{$ext_namespace}/{$location}.html')->display(\$context);\n") + ->write("\$this->env->loadTemplate('@{$ext_namespace}/" . self::TEMPLATE_EVENTS_SUBDIRECTORY . "{$location}.html')->display(\$context);\n") ->write("\$this->env->setNamespaceLookUpOrder(\$previous_look_up_order);\n") ; } -- cgit v1.2.1 From 7f76c9f9c7d9014f313f150a9596bec030f39915 Mon Sep 17 00:00:00 2001 From: rechosen Date: Fri, 9 Aug 2013 11:33:24 +0200 Subject: [ticket/11777] Rename the extension template listener subdirectory to 'event/' Per suggestion of EXreaction and nickvergessen, do not look for extension template event listeners in styles/[style]/template/events/ but in styles/[style]/template/event/ (without the trailing 's') to match the way phpBB looks for php template event listeners. PHPBB3-11777 --- phpBB/phpbb/template/twig/node/event.php | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'phpBB') diff --git a/phpBB/phpbb/template/twig/node/event.php b/phpBB/phpbb/template/twig/node/event.php index 30a9963a77..c94e5fdf20 100644 --- a/phpBB/phpbb/template/twig/node/event.php +++ b/phpBB/phpbb/template/twig/node/event.php @@ -19,9 +19,10 @@ if (!defined('IN_PHPBB')) class phpbb_template_twig_node_event extends Twig_Node { /** - * The subdirectory in which all template event files must be placed + * The subdirectory in which all template listener files must be placed + * @var string */ - const TEMPLATE_EVENTS_SUBDIRECTORY = 'events/'; + protected $listener_directory = 'event/'; /** @var Twig_Environment */ protected $environment; @@ -42,7 +43,7 @@ class phpbb_template_twig_node_event extends Twig_Node { $compiler->addDebugInfo($this); - $location = $this->getNode('expr')->getAttribute('name'); + $location = $this->listener_directory . $this->getNode('expr')->getAttribute('name'); foreach ($this->environment->get_phpbb_extensions() as $ext_namespace => $ext_path) { @@ -55,19 +56,19 @@ class phpbb_template_twig_node_event extends Twig_Node // slower, but makes developing extensions easier (no need to // purge the cache when a new event template file is added) $compiler - ->write("if (\$this->env->getLoader()->exists('@{$ext_namespace}/" . self::TEMPLATE_EVENTS_SUBDIRECTORY . "{$location}.html')) {\n") + ->write("if (\$this->env->getLoader()->exists('@{$ext_namespace}/{$location}.html')) {\n") ->indent() ; } - if (defined('DEBUG') || $this->environment->getLoader()->exists('@' . $ext_namespace . '/' . self::TEMPLATE_EVENTS_SUBDIRECTORY . $location . '.html')) + if (defined('DEBUG') || $this->environment->getLoader()->exists('@' . $ext_namespace . '/' . $location . '.html')) { $compiler ->write("\$previous_look_up_order = \$this->env->getNamespaceLookUpOrder();\n") // We set the namespace lookup order to be this extension first, then the main path ->write("\$this->env->setNamespaceLookUpOrder(array('{$ext_namespace}', '__main__'));\n") - ->write("\$this->env->loadTemplate('@{$ext_namespace}/" . self::TEMPLATE_EVENTS_SUBDIRECTORY . "{$location}.html')->display(\$context);\n") + ->write("\$this->env->loadTemplate('@{$ext_namespace}/{$location}.html')->display(\$context);\n") ->write("\$this->env->setNamespaceLookUpOrder(\$previous_look_up_order);\n") ; } -- cgit v1.2.1 From 08e6c6118036700f5d4196aa09d9d1a34b840514 Mon Sep 17 00:00:00 2001 From: rechosen Date: Fri, 9 Aug 2013 11:39:43 +0200 Subject: [ticket/11777] Require a suffix of '_listener' on extension template listeners To further mirror the file name and location requirements for php template event listeners, require extension template event listener files to follow the '_listener.html' naming format. PHPBB3-11777 --- phpBB/phpbb/template/twig/node/event.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB') diff --git a/phpBB/phpbb/template/twig/node/event.php b/phpBB/phpbb/template/twig/node/event.php index c94e5fdf20..4533151d05 100644 --- a/phpBB/phpbb/template/twig/node/event.php +++ b/phpBB/phpbb/template/twig/node/event.php @@ -43,7 +43,7 @@ class phpbb_template_twig_node_event extends Twig_Node { $compiler->addDebugInfo($this); - $location = $this->listener_directory . $this->getNode('expr')->getAttribute('name'); + $location = $this->listener_directory . $this->getNode('expr')->getAttribute('name') . '_listener'; foreach ($this->environment->get_phpbb_extensions() as $ext_namespace => $ext_path) { -- cgit v1.2.1 From 65d8cd63022d688fe618f128768b78a6214db166 Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Tue, 13 Aug 2013 02:14:22 -0700 Subject: [ticket/11784] Remove naming redundancy for event listeners PHPBB3-11784 --- phpBB/phpbb/event/extension_subscriber_loader.php | 1 - phpBB/phpbb/template/twig/node/event.php | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) (limited to 'phpBB') diff --git a/phpBB/phpbb/event/extension_subscriber_loader.php b/phpBB/phpbb/event/extension_subscriber_loader.php index d933b943d7..d6284a52fb 100644 --- a/phpBB/phpbb/event/extension_subscriber_loader.php +++ b/phpBB/phpbb/event/extension_subscriber_loader.php @@ -33,7 +33,6 @@ class phpbb_event_extension_subscriber_loader $finder = $this->extension_manager->get_finder(); $subscriber_classes = $finder ->extension_directory('/event') - ->suffix('listener') ->core_path('event/') ->get_classes(); diff --git a/phpBB/phpbb/template/twig/node/event.php b/phpBB/phpbb/template/twig/node/event.php index 4533151d05..c94e5fdf20 100644 --- a/phpBB/phpbb/template/twig/node/event.php +++ b/phpBB/phpbb/template/twig/node/event.php @@ -43,7 +43,7 @@ class phpbb_template_twig_node_event extends Twig_Node { $compiler->addDebugInfo($this); - $location = $this->listener_directory . $this->getNode('expr')->getAttribute('name') . '_listener'; + $location = $this->listener_directory . $this->getNode('expr')->getAttribute('name'); foreach ($this->environment->get_phpbb_extensions() as $ext_namespace => $ext_path) { -- cgit v1.2.1 From 9c299b0e8367ec8f9bb631e637b2492483ab3b8a Mon Sep 17 00:00:00 2001 From: Vjacheslav Trushkin Date: Wed, 14 Aug 2013 19:09:27 +0300 Subject: [ticket/11789] Remove colors from HTML code PHPBB3-11789 --- phpBB/styles/subsilver2/template/overall_header.html | 2 +- phpBB/styles/subsilver2/template/ucp_header.html | 4 ++-- phpBB/styles/subsilver2/template/ucp_pm_history.html | 2 +- phpBB/styles/subsilver2/theme/stylesheet.css | 10 +++++++++- 4 files changed, 13 insertions(+), 5 deletions(-) (limited to 'phpBB') diff --git a/phpBB/styles/subsilver2/template/overall_header.html b/phpBB/styles/subsilver2/template/overall_header.html index 62ed79ed39..7eb736aa32 100644 --- a/phpBB/styles/subsilver2/template/overall_header.html +++ b/phpBB/styles/subsilver2/template/overall_header.html @@ -160,7 +160,7 @@ function marklist(id, name, state)
    - - - - - + - -
    - {L_SIGNATURE_EXPLAIN} - - + + -
    + {L_SIGNATURE_EXPLAIN} + @@ -47,11 +44,13 @@ -
    {L_SMILIES}
    {L_MORE_SMILIES}
    - -
    - +
    +
    + + + + @@ -75,11 +74,6 @@
    - -
    -
    + -- cgit v1.2.1 From a10ab3e7d94061d264ee285359565f651e7b9671 Mon Sep 17 00:00:00 2001 From: rechosen Date: Sun, 14 Jul 2013 21:50:22 +0200 Subject: [ticket/9550] Add template event memberlist_view_user_statistics_after Adds the append counterpart of a template event required for the karma extension. It allows adding entries to the user statistics part of any user profile. Explanation from http://area51.phpbb.com/phpBB/viewtopic.php?f=111&t=44379: Per suggestion of nickvergessen, add a counterpart for every append or prepend event. PHPBB3-9550 --- phpBB/styles/prosilver/template/memberlist_view.html | 1 + phpBB/styles/subsilver2/template/memberlist_view.html | 1 + 2 files changed, 2 insertions(+) (limited to 'phpBB') diff --git a/phpBB/styles/prosilver/template/memberlist_view.html b/phpBB/styles/prosilver/template/memberlist_view.html index b339e07d37..0d103b5914 100644 --- a/phpBB/styles/prosilver/template/memberlist_view.html +++ b/phpBB/styles/prosilver/template/memberlist_view.html @@ -93,6 +93,7 @@
    {L_ACTIVE_IN_FORUM}{L_COLON}
    {ACTIVE_FORUM}
    ({ACTIVE_FORUM_POSTS} / {ACTIVE_FORUM_PCT}) -
    {L_ACTIVE_IN_TOPIC}{L_COLON}
    {ACTIVE_TOPIC}
    ({ACTIVE_TOPIC_POSTS} / {ACTIVE_TOPIC_PCT}) -
    + diff --git a/phpBB/styles/subsilver2/template/memberlist_view.html b/phpBB/styles/subsilver2/template/memberlist_view.html index e097e09565..3ffdb1ce70 100644 --- a/phpBB/styles/subsilver2/template/memberlist_view.html +++ b/phpBB/styles/subsilver2/template/memberlist_view.html @@ -97,6 +97,7 @@
    +
    {L_JOINED}{L_COLON} {JOINED}{ACTIVE_TOPIC}
    [ {ACTIVE_TOPIC_POSTS} / {ACTIVE_TOPIC_PCT} ]-
    {L_BACK_TO_TOP}
    {L_BACK_TO_TOP}
    {memberrow.RANK_IMG}{memberrow.RANK_TITLE} {memberrow.USERNAME_FULL}
    {L_SELECT} ]
    {memberrow.RANK_IMG}{memberrow.RANK_TITLE} {memberrow.USERNAME_FULL}
    {L_SELECT} ]
    {memberrow.POSTS}{memberrow.POSTS}
    {memberrow.LOCATION}
     
    {memberrow.JOINED}
     {memberrow.ROW_NUMBER} {memberrow.USERNAME_FULL}{L_SELECT} ]{memberrow.USERNAME_FULL}{L_SELECT} ]  {memberrow.JOINED}  {memberrow.POSTS} {memberrow.RANK_IMG}{memberrow.RANK_TITLE}
    {memberrow.RANK_IMG}{memberrow.RANK_TITLE} {memberrow.USERNAME_FULL}
    {L_SELECT} ]
    {memberrow.RANK_IMG}{memberrow.RANK_TITLE} {memberrow.USERNAME_FULL}
    {L_SELECT} ]
    {memberrow.POSTS}{memberrow.POSTS}
    {memberrow.LOCATION}
     
    {memberrow.JOINED}
     {memberrow.ROW_NUMBER} {memberrow.USERNAME_FULL}{L_SELECT} ]{memberrow.USERNAME_FULL}{L_SELECT} ]  {memberrow.JOINED}  {memberrow.POSTS} {memberrow.RANK_IMG}{memberrow.RANK_TITLE}
    {L_BACK_TO_TOP} + +
    + + + {EDIT_IMG} + {QUOTE_IMG} + + +  
    +
    * {L_LOGIN_LOGOUT}   * {L_RESTORE_PERMISSIONS} -  {L_BOARD_DISABLED} +  {L_BOARD_DISABLED}  * {PRIVATE_MESSAGE_INFO}, {PRIVATE_MESSAGE_INFO_UNREAD} diff --git a/phpBB/styles/subsilver2/template/ucp_header.html b/phpBB/styles/subsilver2/template/ucp_header.html index 1566a15929..4ad27738fa 100644 --- a/phpBB/styles/subsilver2/template/ucp_header.html +++ b/phpBB/styles/subsilver2/template/ucp_header.html @@ -123,7 +123,7 @@
    - {L_FRIENDS_ONLINE} + {L_FRIENDS_ONLINE}
    - style="background-color:lightblue"> + class="current">
    {L_PM_SUBJECT}: {history_row.SUBJECT}
    {L_FOLDER}: {history_row.FOLDER}
    diff --git a/phpBB/styles/subsilver2/theme/stylesheet.css b/phpBB/styles/subsilver2/theme/stylesheet.css index 177a988e93..29db8f2d47 100644 --- a/phpBB/styles/subsilver2/theme/stylesheet.css +++ b/phpBB/styles/subsilver2/theme/stylesheet.css @@ -292,7 +292,11 @@ p.topicdetails { text-decoration: none; } -.error { +.online { + color: green; +} + +.offline, .error { color: red; } @@ -360,6 +364,10 @@ td.profile { background-color: #D1D7DC; } +.current { + background-color: lightblue; +} + hr { height: 1px; border-width: 0; -- cgit v1.2.1 From 48f6f4559c9d3df49b56f0831a89b516f0f2f34f Mon Sep 17 00:00:00 2001 From: rechosen Date: Fri, 16 Aug 2013 17:48:36 +0200 Subject: [ticket/11794] Add missing array element commas to docs/coding-guidelines.html Even though the coding guidelines document prescribes "commas after every array element", it contains several example code fragments with array elements not terminated by a comma. This commit fixes that. PHPBB3-11794 --- phpBB/docs/coding-guidelines.html | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'phpBB') diff --git a/phpBB/docs/coding-guidelines.html b/phpBB/docs/coding-guidelines.html index a541fe8866..f3d161589b 100644 --- a/phpBB/docs/coding-guidelines.html +++ b/phpBB/docs/coding-guidelines.html @@ -728,7 +728,7 @@ $sql = 'SELECT * $sql_ary = array( 'somedata' => $my_string, 'otherdata' => $an_int, - 'moredata' => $another_int + 'moredata' => $another_int, ); $db->sql_query('INSERT INTO ' . SOME_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary)); @@ -740,7 +740,7 @@ $db->sql_query('INSERT INTO ' . SOME_TABLE . ' ' . $db->sql_build_array('I $sql_ary = array( 'somedata' => $my_string, 'otherdata' => $an_int, - 'moredata' => $another_int + 'moredata' => $another_int, ); $sql = 'UPDATE ' . SOME_TABLE . ' @@ -833,20 +833,20 @@ $sql_array = array( 'FROM' => array( FORUMS_WATCH_TABLE => 'fw', - FORUMS_TABLE => 'f' + FORUMS_TABLE => 'f', ), 'LEFT_JOIN' => array( array( 'FROM' => array(FORUMS_TRACK_TABLE => 'ft'), - 'ON' => 'ft.user_id = ' . $user->data['user_id'] . ' AND ft.forum_id = f.forum_id' - ) + 'ON' => 'ft.user_id = ' . $user->data['user_id'] . ' AND ft.forum_id = f.forum_id', + ), ), 'WHERE' => 'fw.user_id = ' . $user->data['user_id'] . ' AND f.forum_id = fw.forum_id', - 'ORDER_BY' => 'left_id' + 'ORDER_BY' => 'left_id', ); $sql = $db->sql_build_query('SELECT', $sql_array); @@ -860,13 +860,13 @@ $sql_array = array( 'FROM' => array( FORUMS_WATCH_TABLE => 'fw', - FORUMS_TABLE => 'f' + FORUMS_TABLE => 'f', ), 'WHERE' => 'fw.user_id = ' . $user->data['user_id'] . ' AND f.forum_id = fw.forum_id', - 'ORDER_BY' => 'left_id' + 'ORDER_BY' => 'left_id', ); if ($config['load_db_lastread']) @@ -874,8 +874,8 @@ if ($config['load_db_lastread']) $sql_array['LEFT_JOIN'] = array( array( 'FROM' => array(FORUMS_TRACK_TABLE => 'ft'), - 'ON' => 'ft.user_id = ' . $user->data['user_id'] . ' AND ft.forum_id = f.forum_id' - ) + 'ON' => 'ft.user_id = ' . $user->data['user_id'] . ' AND ft.forum_id = f.forum_id', + ), ); $sql_array['SELECT'] .= ', ft.mark_time '; -- cgit v1.2.1 From 87dd739a84375958af618605e580127f3d0e1784 Mon Sep 17 00:00:00 2001 From: Vjacheslav Trushkin Date: Fri, 16 Aug 2013 18:51:31 +0300 Subject: [ticket/11796] Replace pagination with pagination.html PHPBB3-11796 --- phpBB/styles/prosilver/template/mcp_forum.html | 15 +-------------- phpBB/styles/prosilver/template/mcp_warn_list.html | 15 +-------------- phpBB/styles/prosilver/template/search_results.html | 15 +-------------- phpBB/styles/prosilver/template/viewtopic_body.html | 12 +----------- 4 files changed, 4 insertions(+), 53 deletions(-) (limited to 'phpBB') diff --git a/phpBB/styles/prosilver/template/mcp_forum.html b/phpBB/styles/prosilver/template/mcp_forum.html index 45e6c10d46..e5dcb94855 100644 --- a/phpBB/styles/prosilver/template/mcp_forum.html +++ b/phpBB/styles/prosilver/template/mcp_forum.html @@ -100,20 +100,7 @@