diff options
Diffstat (limited to 'phpBB/includes')
28 files changed, 2742 insertions, 77 deletions
diff --git a/phpBB/includes/acp/acp_board.php b/phpBB/includes/acp/acp_board.php index 322e1c55d8..960cc89db5 100644 --- a/phpBB/includes/acp/acp_board.php +++ b/phpBB/includes/acp/acp_board.php @@ -413,8 +413,8 @@ class acp_board 'board_email_form' => array('lang' => 'BOARD_EMAIL_FORM', 'validate' => 'bool', 'type' => 'radio:enabled_disabled', 'explain' => true), 'email_function_name' => array('lang' => 'EMAIL_FUNCTION_NAME', 'validate' => 'string', 'type' => 'text:20:50', 'explain' => true), 'email_package_size' => array('lang' => 'EMAIL_PACKAGE_SIZE', 'validate' => 'int:0', 'type' => 'text:5:5', 'explain' => true), - 'board_contact' => array('lang' => 'CONTACT_EMAIL', 'validate' => 'string', 'type' => 'text:25:100', 'explain' => true), - 'board_email' => array('lang' => 'ADMIN_EMAIL', 'validate' => 'string', 'type' => 'text:25:100', 'explain' => true), + 'board_contact' => array('lang' => 'CONTACT_EMAIL', 'validate' => 'email', 'type' => 'text:25:100', 'explain' => true), + 'board_email' => array('lang' => 'ADMIN_EMAIL', 'validate' => 'email', 'type' => 'text:25:100', 'explain' => true), 'board_email_sig' => array('lang' => 'EMAIL_SIG', 'validate' => 'string', 'type' => 'textarea:5:30', 'explain' => true), 'board_hide_emails' => array('lang' => 'BOARD_HIDE_EMAILS', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), diff --git a/phpBB/includes/acp/acp_extensions.php b/phpBB/includes/acp/acp_extensions.php index a0bcf62ecc..24211196bd 100644 --- a/phpBB/includes/acp/acp_extensions.php +++ b/phpBB/includes/acp/acp_extensions.php @@ -37,7 +37,7 @@ class acp_extensions $this->template = $template; $this->user = $user; - $user->add_lang(array('install', 'acp/extensions')); + $user->add_lang(array('install', 'acp/extensions', 'migrator')); $this->page_title = 'ACP_EXTENSIONS'; @@ -103,11 +103,18 @@ class acp_extensions trigger_error($user->lang['EXTENSION_NOT_AVAILABLE'] . adm_back_link($this->u_action)); } - if ($phpbb_extension_manager->enable_step($ext_name)) + try { - $template->assign_var('S_NEXT_STEP', true); + if ($phpbb_extension_manager->enable_step($ext_name)) + { + $template->assign_var('S_NEXT_STEP', true); - meta_refresh(0, $this->u_action . '&action=enable&ext_name=' . urlencode($ext_name)); + meta_refresh(0, $this->u_action . '&action=enable&ext_name=' . urlencode($ext_name)); + } + } + catch (phpbb_db_migration_exception $e) + { + $template->assign_var('MIGRATOR_ERROR', $e->getLocalisedMessage($user)); } $this->tpl_name = 'acp_ext_enable'; @@ -156,11 +163,18 @@ class acp_extensions break; case 'purge': - if ($phpbb_extension_manager->purge_step($ext_name)) + try { - $template->assign_var('S_NEXT_STEP', true); + if ($phpbb_extension_manager->purge_step($ext_name)) + { + $template->assign_var('S_NEXT_STEP', true); - meta_refresh(0, $this->u_action . '&action=purge&ext_name=' . urlencode($ext_name)); + meta_refresh(0, $this->u_action . '&action=purge&ext_name=' . urlencode($ext_name)); + } + } + catch (phpbb_db_migration_exception $e) + { + $template->assign_var('MIGRATOR_ERROR', $e->getLocalisedMessage($user)); } $this->tpl_name = 'acp_ext_purge'; diff --git a/phpBB/includes/acp/acp_groups.php b/phpBB/includes/acp/acp_groups.php index 9145a20400..21b1d4b837 100644 --- a/phpBB/includes/acp/acp_groups.php +++ b/phpBB/includes/acp/acp_groups.php @@ -126,13 +126,34 @@ class acp_groups { trigger_error($user->lang['NO_GROUP'] . adm_back_link($this->u_action), E_USER_WARNING); } + else if (empty($mark_ary)) + { + trigger_error($user->lang['NO_USERS'] . adm_back_link($this->u_action . '&action=list&g=' . $group_id), E_USER_WARNING); + } if (confirm_box(true)) { $group_name = ($group_row['group_type'] == GROUP_SPECIAL) ? $user->lang['G_' . $group_row['group_name']] : $group_row['group_name']; + group_user_attributes('default', $group_id, $mark_ary, false, $group_name, $group_row); + trigger_error($user->lang['GROUP_DEFS_UPDATED'] . adm_back_link($this->u_action . '&action=list&g=' . $group_id)); + } + else + { + confirm_box(false, $user->lang['CONFIRM_OPERATION'], build_hidden_fields(array( + 'mark' => $mark_ary, + 'g' => $group_id, + 'i' => $id, + 'mode' => $mode, + 'action' => $action)) + ); + } - if (!sizeof($mark_ary)) + break; + case 'set_default_on_all': + if (confirm_box(true)) { + $group_name = ($group_row['group_type'] == GROUP_SPECIAL) ? $user->lang['G_' . $group_row['group_name']] : $group_row['group_name']; + $start = 0; do @@ -163,28 +184,25 @@ class acp_groups $db->sql_freeresult($result); } while ($start); + + trigger_error($user->lang['GROUP_DEFS_UPDATED'] . adm_back_link($this->u_action . '&action=list&g=' . $group_id)); } else { - group_user_attributes('default', $group_id, $mark_ary, false, $group_name, $group_row); + confirm_box(false, $user->lang['CONFIRM_OPERATION'], build_hidden_fields(array( + 'mark' => $mark_ary, + 'g' => $group_id, + 'i' => $id, + 'mode' => $mode, + 'action' => $action)) + ); } - - trigger_error($user->lang['GROUP_DEFS_UPDATED'] . adm_back_link($this->u_action . '&action=list&g=' . $group_id)); - } - else - { - confirm_box(false, $user->lang['CONFIRM_OPERATION'], build_hidden_fields(array( - 'mark' => $mark_ary, - 'g' => $group_id, - 'i' => $id, - 'mode' => $mode, - 'action' => $action)) - ); - } - break; - case 'deleteusers': + if (empty($mark_ary)) + { + trigger_error($user->lang['NO_USERS'] . adm_back_link($this->u_action . '&action=list&g=' . $group_id), E_USER_WARNING); + } case 'delete': if (!$group_id) { @@ -698,7 +716,7 @@ class acp_groups 'U_ACTION' => $this->u_action . "&g=$group_id", 'U_BACK' => $this->u_action, 'U_FIND_USERNAME' => append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=searchuser&form=list&field=usernames'), - 'U_DEFAULT_ALL' => "{$this->u_action}&action=default&g=$group_id", + 'U_DEFAULT_ALL' => "{$this->u_action}&action=set_default_on_all&g=$group_id", )); // Grab the members diff --git a/phpBB/includes/acp/acp_users.php b/phpBB/includes/acp/acp_users.php index 2bdbf1441a..82d8ef5cbb 100644 --- a/phpBB/includes/acp/acp_users.php +++ b/phpBB/includes/acp/acp_users.php @@ -1352,6 +1352,7 @@ class acp_users $data = array( 'icq' => request_var('icq', $user_row['user_icq']), 'aim' => request_var('aim', $user_row['user_aim']), + 'msn' => request_var('msn', $user_row['user_msnm']), 'yim' => request_var('yim', $user_row['user_yim']), 'jabber' => utf8_normalize_nfc(request_var('jabber', $user_row['user_jabber'], true)), 'website' => request_var('website', $user_row['user_website']), @@ -1381,6 +1382,7 @@ class acp_users array('string', true, 3, 15), array('match', true, '#^[0-9]+$#i')), 'aim' => array('string', true, 3, 255), + 'msn' => array('string', true, 5, 255), 'jabber' => array( array('string', true, 5, 255), array('jabber')), @@ -1414,6 +1416,7 @@ class acp_users $sql_ary = array( 'user_icq' => $data['icq'], 'user_aim' => $data['aim'], + 'user_msnm' => $data['msn'], 'user_yim' => $data['yim'], 'user_jabber' => $data['jabber'], 'user_website' => $data['website'], @@ -1466,6 +1469,7 @@ class acp_users 'ICQ' => $data['icq'], 'YIM' => $data['yim'], 'AIM' => $data['aim'], + 'MSN' => $data['msn'], 'JABBER' => $data['jabber'], 'WEBSITE' => $data['website'], 'LOCATION' => $data['location'], diff --git a/phpBB/includes/acp/info/acp_extensions.php b/phpBB/includes/acp/info/acp_extensions.php index 03d7059165..174b365af0 100644 --- a/phpBB/includes/acp/info/acp_extensions.php +++ b/phpBB/includes/acp/info/acp_extensions.php @@ -16,10 +16,10 @@ class acp_extensions_info { return array( 'filename' => 'acp_extensions', - 'title' => 'ACP_EXTENSIONS_MANAGEMENT', + 'title' => 'ACP_EXTENSION_MANAGEMENT', 'version' => '1.0.0', 'modes' => array( - 'main' => array('title' => 'ACP_EXTENSIONS', 'auth' => 'acl_a_extensions', 'cat' => array('ACP_EXTENSIONS_MANAGEMENT')), + 'main' => array('title' => 'ACP_EXTENSIONS', 'auth' => 'acl_a_extensions', 'cat' => array('ACP_EXTENSION_MANAGEMENT')), ), ); } diff --git a/phpBB/includes/constants.php b/phpBB/includes/constants.php index 68af41ab20..68c96a2759 100644 --- a/phpBB/includes/constants.php +++ b/phpBB/includes/constants.php @@ -237,6 +237,7 @@ define('ICONS_TABLE', $table_prefix . 'icons'); define('LANG_TABLE', $table_prefix . 'lang'); define('LOG_TABLE', $table_prefix . 'log'); define('LOGIN_ATTEMPT_TABLE', $table_prefix . 'login_attempts'); +define('MIGRATIONS_TABLE', $table_prefix . 'migrations'); define('MODERATOR_CACHE_TABLE', $table_prefix . 'moderator_cache'); define('MODULES_TABLE', $table_prefix . 'modules'); define('POLL_OPTIONS_TABLE', $table_prefix . 'poll_options'); diff --git a/phpBB/includes/datetime.php b/phpBB/includes/datetime.php index b3462ddf67..3c6d4971b9 100644 --- a/phpBB/includes/datetime.php +++ b/phpBB/includes/datetime.php @@ -143,7 +143,7 @@ class phpbb_datetime extends DateTime 'is_short' => strpos($format, self::RELATIVE_WRAPPER) !== false, 'format_short' => substr($format, 0, strpos($format, self::RELATIVE_WRAPPER)) . self::RELATIVE_WRAPPER . self::RELATIVE_WRAPPER . substr(strrchr($format, self::RELATIVE_WRAPPER), 1), 'format_long' => str_replace(self::RELATIVE_WRAPPER, '', $format), - 'lang' => $user->lang['datetime'], + 'lang' => array_filter($user->lang['datetime'], 'is_string'), ); // Short representation of month in format? Some languages use different terms for the long and short format of May diff --git a/phpBB/includes/db/db_tools.php b/phpBB/includes/db/db_tools.php index a4bf40fcd7..983cdc18ea 100644 --- a/phpBB/includes/db/db_tools.php +++ b/phpBB/includes/db/db_tools.php @@ -303,7 +303,7 @@ class phpbb_db_tools * @param phpbb_db_driver $db Database connection * @param bool $return_statements True if only statements should be returned and no SQL being executed */ - function phpbb_db_tools(&$db, $return_statements = false) + function phpbb_db_tools(phpbb_db_driver $db, $return_statements = false) { $this->db = $db; $this->return_statements = $return_statements; @@ -346,6 +346,17 @@ class phpbb_db_tools } /** + * Setter for {@link $return_statements return_statements}. + * + * @param bool $return_statements True if SQL should not be executed but returned as strings + * @return null + */ + public function set_return_statements($return_statements) + { + $this->return_statements = $return_statements; + } + + /** * Gets a list of tables in the database. * * @return array Array of table names (all lower case) @@ -674,6 +685,8 @@ class phpbb_db_tools * Handle passed database update array. * Expected structure... * Key being one of the following + * drop_tables: Drop tables + * add_tables: Add tables * change_columns: Column changes (only type, not name) * add_columns: Add columns to a table * drop_keys: Dropping keys diff --git a/phpBB/includes/db/migration/exception.php b/phpBB/includes/db/migration/exception.php new file mode 100644 index 0000000000..e84330dd71 --- /dev/null +++ b/phpBB/includes/db/migration/exception.php @@ -0,0 +1,79 @@ +<?php +/** +* +* @package db +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* The migrator is responsible for applying new migrations in the correct order. +* +* @package db +*/ +class phpbb_db_migration_exception extends \Exception +{ + /** + * Extra parameters sent to exception to aid in debugging + * @var array + */ + protected $parameters; + + /** + * Throw an exception. + * + * First argument is the error message. + * Additional arguments will be output with the error message. + */ + public function __construct() + { + $parameters = func_get_args(); + $message = array_shift($parameters); + parent::__construct($message); + + $this->parameters = $parameters; + } + + /** + * Output the error as a string + * + * @return string + */ + public function __toString() + { + return $this->message . ': ' . var_export($this->parameters, true); + } + + /** + * Get the parameters + * + * @return array + */ + public function getParameters() + { + return $this->parameters; + } + + /** + * Get localised message (with $user->lang()) + * + * @param phpbb_user $user + * @return string + */ + public function getLocalisedMessage(phpbb_user $user) + { + $parameters = $this->getParameters(); + array_unshift($parameters, $this->getMessage()); + + return call_user_func_array(array($user, 'lang'), $parameters); + } +} diff --git a/phpBB/includes/db/migration/migration.php b/phpBB/includes/db/migration/migration.php new file mode 100644 index 0000000000..5f14a6953c --- /dev/null +++ b/phpBB/includes/db/migration/migration.php @@ -0,0 +1,190 @@ +<?php +/** +* +* @package db +* @copyright (c) 2011 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* Abstract base class for database migrations +* +* Each migration consists of a set of schema and data changes to be implemented +* in a subclass. This class provides various utility methods to simplify editing +* a phpBB. +* +* @package db +*/ +abstract class phpbb_db_migration +{ + /** @var phpbb_config */ + protected $config; + + /** @var phpbb_db_driver */ + protected $db; + + /** @var phpbb_db_tools */ + protected $db_tools; + + /** @var string */ + protected $table_prefix; + + /** @var string */ + protected $phpbb_root_path; + + /** @var string */ + protected $php_ext; + + /** @var array Errors, if any occured */ + protected $errors; + + /** @var array List of queries executed through $this->sql_query() */ + protected $queries = array(); + + /** + * Constructor + * + * @param phpbb_config $config + * @param phpbb_db_driver $db + * @param phpbb_db_tools $db_tools + * @param string $phpbb_root_path + * @param string $php_ext + * @param string $table_prefix + */ + public function __construct(phpbb_config $config, phpbb_db_driver $db, phpbb_db_tools $db_tools, $phpbb_root_path, $php_ext, $table_prefix) + { + $this->config = $config; + $this->db = $db; + $this->db_tools = $db_tools; + $this->table_prefix = $table_prefix; + + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + + $this->errors = array(); + } + + /** + * Defines other migrations to be applied first + * + * @return array An array of migration class names + */ + static public function depends_on() + { + return array(); + } + + /** + * Allows you to check if the migration is effectively installed (entirely optional) + * + * This is checked when a migration is installed. If true is returned, the migration will be set as + * installed without performing the database changes. + * This function is intended to help moving to migrations from a previous database updater, where some + * migrations may have been installed already even though they are not yet listed in the migrations table. + * + * @return bool True if this migration is installed, False if this migration is not installed (checked on install) + */ + public function effectively_installed() + { + return false; + } + + /** + * Updates the database schema by providing a set of change instructions + * + * @return array Array of schema changes (compatible with db_tools->perform_schema_changes()) + */ + public function update_schema() + { + return array(); + } + + /** + * Reverts the database schema by providing a set of change instructions + * + * @return array Array of schema changes (compatible with db_tools->perform_schema_changes()) + */ + public function revert_schema() + { + return array(); + } + + /** + * Updates data by returning a list of instructions to be executed + * + * @return array Array of data update instructions + */ + public function update_data() + { + return array(); + } + + /** + * Reverts data by returning a list of instructions to be executed + * + * @return array Array of data instructions that will be performed on revert + * NOTE: calls to tools (such as config.add) are automatically reverted when + * possible, so you should not attempt to revert those, this is mostly for + * otherwise unrevertable calls (custom functions for example) + */ + public function revert_data() + { + return array(); + } + + /** + * Wrapper for running queries to generate user feedback on updates + * + * @param string $sql SQL query to run on the database + * @return mixed Query result from db->sql_query() + */ + protected function sql_query($sql) + { + $this->queries[] = $sql; + + $this->db->sql_return_on_error(true); + + if ($sql === 'begin') + { + $result = $this->db->sql_transaction('begin'); + } + else if ($sql === 'commit') + { + $result = $this->db->sql_transaction('commit'); + } + else + { + $result = $this->db->sql_query($sql); + if ($this->db->sql_error_triggered) + { + $this->errors[] = array( + 'sql' => $this->db->sql_error_sql, + 'code' => $this->db->sql_error_returned, + ); + } + } + + $this->db->sql_return_on_error(false); + + return $result; + } + + /** + * Get the list of queries run + * + * @return array + */ + public function get_queries() + { + return $this->queries; + } +} diff --git a/phpBB/includes/db/migration/tool/config.php b/phpBB/includes/db/migration/tool/config.php new file mode 100644 index 0000000000..458a25fb66 --- /dev/null +++ b/phpBB/includes/db/migration/tool/config.php @@ -0,0 +1,150 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +/** +* Migration config tool +* +* @package db +*/ +class phpbb_db_migration_tool_config implements phpbb_db_migration_tool_interface +{ + /** @var phpbb_config */ + protected $config; + + /** + * Constructor + * + * @param phpbb_config $config + */ + public function __construct(phpbb_config $config) + { + $this->config = $config; + } + + /** + * {@inheritdoc} + */ + public function get_name() + { + return 'config'; + } + + /** + * Add a config setting. + * + * @param string $config_name The name of the config setting + * you would like to add + * @param mixed $config_value The value of the config setting + * @param bool $is_dynamic True if it is dynamic (changes very often) + * and should not be stored in the cache, false if not. + * @return null + */ + public function add($config_name, $config_value, $is_dynamic = false) + { + if (isset($this->config[$config_name])) + { + throw new phpbb_db_migration_exception('CONFIG_ALREADY_EXIST', $config_name); + } + + $this->config->set($config_name, $config_value, !$is_dynamic); + } + + /** + * Update an existing config setting. + * + * @param string $config_name The name of the config setting you would + * like to update + * @param mixed $config_value The value of the config setting + * @return null + */ + public function update($config_name, $config_value) + { + if (!isset($this->config[$config_name])) + { + throw new phpbb_db_migration_exception('CONFIG_NOT_EXIST', $config_name); + } + + $this->config->set($config_name, $config_value); + } + + /** + * Update a config setting if the first argument equal to the + * current config value + * + * @param string $compare If equal to the current config value, will be + * updated to the new config value, otherwise not + * @param string $config_name The name of the config setting you would + * like to update + * @param mixed $config_value The value of the config setting + * @return null + */ + public function update_if_equals($compare, $config_name, $config_value) + { + if (!isset($this->config[$config_name])) + { + throw new phpbb_db_migration_exception('CONFIG_NOT_EXIST', $config_name); + } + + $this->config->set_atomic($config_name, $compare, $config_value); + } + + /** + * Remove an existing config setting. + * + * @param string $config_name The name of the config setting you would + * like to remove + * @return null + */ + public function remove($config_name) + { + if (!isset($this->config[$config_name])) + { + throw new phpbb_db_migration_exception('CONFIG_NOT_EXIST', $config_name); + } + + $this->config->delete($config_name); + } + + /** + * {@inheritdoc} + */ + public function reverse() + { + $arguments = func_get_args(); + $original_call = array_shift($arguments); + + $call = false; + switch ($original_call) + { + case 'add': + $call = 'remove'; + break; + + case 'remove': + $call = 'add'; + break; + + case 'update_if_equals': + $call = 'update_if_equals'; + + // Set to the original value if the current value is what we compared to originally + $arguments = array( + $arguments[2], + $arguments[1], + $arguments[0], + ); + break; + } + + if ($call) + { + return call_user_func_array(array(&$this, $call), $arguments); + } + } +} diff --git a/phpBB/includes/db/migration/tool/interface.php b/phpBB/includes/db/migration/tool/interface.php new file mode 100644 index 0000000000..ced53b2023 --- /dev/null +++ b/phpBB/includes/db/migration/tool/interface.php @@ -0,0 +1,33 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +/** +* Migration tool interface +* +* @package db +*/ +interface phpbb_db_migration_tool_interface +{ + /** + * Retrieve a short name used for commands in migrations. + * + * @return string short name + */ + public function get_name(); + + /** + * Reverse an original install action + * + * First argument is the original call to the class (e.g. add, remove) + * After the first argument, send the original arguments to the function in the original call + * + * @return null + */ + public function reverse(); +} diff --git a/phpBB/includes/db/migration/tool/module.php b/phpBB/includes/db/migration/tool/module.php new file mode 100644 index 0000000000..4d7fae2bb0 --- /dev/null +++ b/phpBB/includes/db/migration/tool/module.php @@ -0,0 +1,513 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +/** +* Migration module management tool +* +* @package db +*/ +class phpbb_db_migration_tool_module implements phpbb_db_migration_tool_interface +{ + /** @var phpbb_cache_service */ + protected $cache; + + /** @var dbal */ + protected $db; + + /** @var phpbb_user */ + protected $user; + + /** @var string */ + protected $phpbb_root_path; + + /** @var string */ + protected $php_ext; + + /** @var string */ + protected $modules_table; + + /** + * Constructor + * + * @param phpbb_db_driver $db + * @param mixed $cache + * @param phpbb_user $user + * @param string $phpbb_root_path + * @param string $php_ext + * @param string $modules_table + */ + public function __construct(phpbb_db_driver $db, phpbb_cache_service $cache, phpbb_user $user, $phpbb_root_path, $php_ext, $modules_table) + { + $this->db = $db; + $this->cache = $cache; + $this->user = $user; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + $this->modules_table = $modules_table; + } + + /** + * {@inheritdoc} + */ + public function get_name() + { + return 'module'; + } + + /** + * Module Exists + * + * Check if a module exists + * + * @param string $class The module class(acp|mcp|ucp) + * @param int|string|bool $parent The parent module_id|module_langname (0 for no parent). + * Use false to ignore the parent check and check class wide. + * @param int|string $module The module_id|module_langname you would like to + * check for to see if it exists + * @return bool true/false if module exists + */ + public function exists($class, $parent, $module) + { + // the main root directory should return true + if (!$module) + { + return true; + } + + $parent_sql = ''; + if ($parent !== false) + { + // Allows '' to be sent as 0 + $parent = $parent ?: 0; + + if (!is_numeric($parent)) + { + $sql = 'SELECT module_id + FROM ' . $this->modules_table . " + WHERE module_langname = '" . $this->db->sql_escape($parent) . "' + AND module_class = '" . $this->db->sql_escape($class) . "'"; + $result = $this->db->sql_query($sql); + $module_id = $this->db->sql_fetchfield('module_id'); + $this->db->sql_freeresult($result); + + if (!$module_id) + { + return false; + } + + $parent_sql = 'AND parent_id = ' . (int) $module_id; + } + else + { + $parent_sql = 'AND parent_id = ' . (int) $parent; + } + } + + $sql = 'SELECT module_id + FROM ' . $this->modules_table . " + WHERE module_class = '" . $this->db->sql_escape($class) . "' + $parent_sql + AND " . ((is_numeric($module)) ? 'module_id = ' . (int) $module : "module_langname = '" . $this->db->sql_escape($module) . "'"); + $result = $this->db->sql_query($sql); + $module_id = $this->db->sql_fetchfield('module_id'); + $this->db->sql_freeresult($result); + + if ($module_id) + { + return true; + } + + return false; + } + + /** + * Module Add + * + * Add a new module + * + * @param string $class The module class(acp|mcp|ucp) + * @param int|string $parent The parent module_id|module_langname (0 for no parent) + * @param array $data an array of the data on the new module. + * This can be setup in two different ways. + * 1. The "manual" way. For inserting a category or one at a time. + * It will be merged with the base array shown a bit below, + * but at the least requires 'module_langname' to be sent, and, + * if you want to create a module (instead of just a category) you must + * send module_basename and module_mode. + * array( + * 'module_enabled' => 1, + * 'module_display' => 1, + * 'module_basename' => '', + * 'module_class' => $class, + * 'parent_id' => (int) $parent, + * 'module_langname' => '', + * 'module_mode' => '', + * 'module_auth' => '', + * ) + * 2. The "automatic" way. For inserting multiple at a time based on the + * specs in the info file for the module(s). For this to work the + * modules must be correctly setup in the info file. + * An example follows (this would insert the settings, log, and flag + * modes from the includes/acp/info/acp_asacp.php file): + * array( + * 'module_basename' => 'asacp', + * 'modes' => array('settings', 'log', 'flag'), + * ) + * Optionally you may not send 'modes' and it will insert all of the + * modules in that info file. + * @param string|bool $include_path If you would like to use a custom include + * path, specify that here + * @return null + */ + public function add($class, $parent = 0, $data = array(), $include_path = false) + { + // Allows '' to be sent as 0 + $parent = $parent ?: 0; + + // allow sending the name as a string in $data to create a category + if (!is_array($data)) + { + $data = array('module_langname' => $data); + } + + if (!isset($data['module_langname'])) + { + // The "automatic" way + $basename = (isset($data['module_basename'])) ? $data['module_basename'] : ''; + $basename = str_replace(array('/', '\\'), '', $basename); + $class = str_replace(array('/', '\\'), '', $class); + + $include_path = ($include_path === false) ? $this->phpbb_root_path . 'includes/' : $include_path; + $info_file = "$class/info/$basename.{$this->php_ext}"; + + // The manual and automatic ways both failed... + if (!file_exists($include_path . $info_file)) + { + throw new phpbb_db_migration_exception('MODULE_INFO_FILE_NOT_EXIST', $class, $info_file); + } + + $classname = "{$basename}_info"; + + if (!class_exists($classname)) + { + include($include_path . $info_file); + } + + $info = new $classname; + $module = $info->module(); + unset($info); + + $result = ''; + foreach ($module['modes'] as $mode => $module_info) + { + if (!isset($data['modes']) || in_array($mode, $data['modes'])) + { + $new_module = array( + 'module_basename' => $basename, + 'module_langname' => $module_info['title'], + 'module_mode' => $mode, + 'module_auth' => $module_info['auth'], + 'module_display' => (isset($module_info['display'])) ? $module_info['display'] : true, + 'before' => (isset($module_info['before'])) ? $module_info['before'] : false, + 'after' => (isset($module_info['after'])) ? $module_info['after'] : false, + ); + + // Run the "manual" way with the data we've collected. + $this->add($class, $parent, $new_module); + } + } + + return; + } + + // The "manual" way + $module_log_name = ((isset($this->user->lang[$data['module_langname']])) ? $this->user->lang[$data['module_langname']] : $data['module_langname']); + add_log('admin', 'LOG_MODULE_ADD', $module_log_name); + + if (!is_numeric($parent)) + { + $sql = 'SELECT module_id + FROM ' . $this->modules_table . " + WHERE module_langname = '" . $this->db->sql_escape($parent) . "' + AND module_class = '" . $this->db->sql_escape($class) . "'"; + $result = $this->db->sql_query($sql); + $module_id = $this->db->sql_fetchfield('module_id'); + $this->db->sql_freeresult($result); + + if (!$module_id) + { + throw new phpbb_db_migration_exception('MODULE_NOT_EXIST', $parent); + } + + $parent = $data['parent_id'] = $module_id; + } + else if (!$this->exists($class, false, $parent)) + { + throw new phpbb_db_migration_exception('MODULE_NOT_EXIST', $parent); + } + + if ($this->exists($class, $parent, $data['module_langname'])) + { + throw new phpbb_db_migration_exception('MODULE_ALREADY_EXIST', $data['module_langname']); + } + + if (!class_exists('acp_modules')) + { + include($this->phpbb_root_path . 'includes/acp/acp_modules.' . $this->php_ext); + $this->user->add_lang('acp/modules'); + } + $acp_modules = new acp_modules(); + + $module_data = array( + 'module_enabled' => (isset($data['module_enabled'])) ? $data['module_enabled'] : 1, + 'module_display' => (isset($data['module_display'])) ? $data['module_display'] : 1, + 'module_basename' => (isset($data['module_basename'])) ? $data['module_basename'] : '', + 'module_class' => $class, + 'parent_id' => (int) $parent, + 'module_langname' => (isset($data['module_langname'])) ? $data['module_langname'] : '', + 'module_mode' => (isset($data['module_mode'])) ? $data['module_mode'] : '', + 'module_auth' => (isset($data['module_auth'])) ? $data['module_auth'] : '', + ); + $result = $acp_modules->update_module_data($module_data, true); + + // update_module_data can either return a string or an empty array... + if (is_string($result)) + { + // Error + throw new phpbb_db_migration_exception('MODULE_ERROR', $result); + } + else + { + // Success + + // Move the module if requested above/below an existing one + if (isset($data['before']) && $data['before']) + { + $sql = 'SELECT left_id + FROM ' . $this->modules_table . " + WHERE module_class = '" . $this->db->sql_escape($class) . "' + AND parent_id = " . (int) $parent . " + AND module_langname = '" . $this->db->sql_escape($data['before']) . "'"; + $this->db->sql_query($sql); + $to_left = (int) $this->db->sql_fetchfield('left_id'); + + $sql = 'UPDATE ' . $this->modules_table . " + SET left_id = left_id + 2, right_id = right_id + 2 + WHERE module_class = '" . $this->db->sql_escape($class) . "' + AND left_id >= $to_left + AND left_id < {$module_data['left_id']}"; + $this->db->sql_query($sql); + + $sql = 'UPDATE ' . $this->modules_table . " + SET left_id = $to_left, right_id = " . ($to_left + 1) . " + WHERE module_class = '" . $this->db->sql_escape($class) . "' + AND module_id = {$module_data['module_id']}"; + $this->db->sql_query($sql); + } + else if (isset($data['after']) && $data['after']) + { + $sql = 'SELECT right_id + FROM ' . $this->modules_table . " + WHERE module_class = '" . $this->db->sql_escape($class) . "' + AND parent_id = " . (int) $parent . " + AND module_langname = '" . $this->db->sql_escape($data['after']) . "'"; + $this->db->sql_query($sql); + $to_right = (int) $this->db->sql_fetchfield('right_id'); + + $sql = 'UPDATE ' . $this->modules_table . " + SET left_id = left_id + 2, right_id = right_id + 2 + WHERE module_class = '" . $this->db->sql_escape($class) . "' + AND left_id >= $to_right + AND left_id < {$module_data['left_id']}"; + $this->db->sql_query($sql); + + $sql = 'UPDATE ' . $this->modules_table . ' + SET left_id = ' . ($to_right + 1) . ', right_id = ' . ($to_right + 2) . " + WHERE module_class = '" . $this->db->sql_escape($class) . "' + AND module_id = {$module_data['module_id']}"; + $this->db->sql_query($sql); + } + } + + // Clear the Modules Cache + $this->cache->destroy("_modules_$class"); + } + + /** + * Module Remove + * + * Remove a module + * + * @param string $class The module class(acp|mcp|ucp) + * @param int|string|bool $parent The parent module_id|module_langname(0 for no parent). + * Use false to ignore the parent check and check class wide. + * @param int|string $module The module id|module_langname + * @param string|bool $include_path If you would like to use a custom include path, + * specify that here + * @return null + */ + public function remove($class, $parent = 0, $module = '', $include_path = false) + { + // Imitation of module_add's "automatic" and "manual" method so the uninstaller works from the same set of instructions for umil_auto + if (is_array($module)) + { + if (isset($module['module_langname'])) + { + // Manual Method + return $this->remove($class, $parent, $module['module_langname'], $include_path); + } + + // Failed. + if (!isset($module['module_basename'])) + { + throw new phpbb_db_migration_exception('MODULE_NOT_EXIST'); + } + + // Automatic method + $basename = str_replace(array('/', '\\'), '', $module['module_basename']); + $class = str_replace(array('/', '\\'), '', $class); + + $include_path = ($include_path === false) ? $this->phpbb_root_path . 'includes/' : $include_path; + $info_file = "$class/info/$basename.{$this->php_ext}"; + + if (!file_exists($include_path . $info_file)) + { + throw new phpbb_db_migration_exception('MODULE_NOT_EXIST', $info_file); + } + + $classname = "{$basename}_info"; + + if (!class_exists($classname)) + { + include($include_path . $info_file); + } + + $info = new $classname; + $module_info = $info->module(); + unset($info); + + foreach ($module_info['modes'] as $mode => $info) + { + if (!isset($module['modes']) || in_array($mode, $module['modes'])) + { + $this->remove($class, $parent, $info['title']) . '<br />'; + } + } + } + else + { + if (!$this->exists($class, $parent, $module)) + { + throw new phpbb_db_migration_exception('MODULE_NOT_EXIST', ((isset($this->user->lang[$module])) ? $this->user->lang[$module] : $module)); + } + + $parent_sql = ''; + if ($parent !== false) + { + // Allows '' to be sent as 0 + $parent = ($parent) ?: 0; + + if (!is_numeric($parent)) + { + $sql = 'SELECT module_id + FROM ' . $this->modules_table . " + WHERE module_langname = '" . $this->db->sql_escape($parent) . "' + AND module_class = '" . $this->db->sql_escape($class) . "'"; + $result = $this->db->sql_query($sql); + $module_id = $this->db->sql_fetchfield('module_id'); + $this->db->sql_freeresult($result); + + // we know it exists from the module_exists check + $parent_sql = 'AND parent_id = ' . (int) $module_id; + } + else + { + $parent_sql = 'AND parent_id = ' . (int) $parent; + } + } + + $module_ids = array(); + if (!is_numeric($module)) + { + $sql = 'SELECT module_id + FROM ' . $this->modules_table . " + WHERE module_langname = '" . $this->db->sql_escape($module) . "' + AND module_class = '" . $this->db->sql_escape($class) . "' + $parent_sql"; + $result = $this->db->sql_query($sql); + while ($module_id = $this->db->sql_fetchfield('module_id')) + { + $module_ids[] = (int) $module_id; + } + $this->db->sql_freeresult($result); + + $module_name = $module; + } + else + { + $module = (int) $module; + $sql = 'SELECT module_langname + FROM ' . $this->modules_table . " + WHERE module_id = $module + AND module_class = '" . $this->db->sql_escape($class) . "' + $parent_sql"; + $result = $this->db->sql_query($sql); + $module_name = $this->db->sql_fetchfield('module_id'); + $this->db->sql_freeresult($result); + + $module_ids[] = $module; + } + + if (!class_exists('acp_modules')) + { + include($this->phpbb_root_path . 'includes/acp/acp_modules.' . $this->php_ext); + $this->user->add_lang('acp/modules'); + } + $acp_modules = new acp_modules(); + $acp_modules->module_class = $class; + + foreach ($module_ids as $module_id) + { + $result = $acp_modules->delete_module($module_id); + if (!empty($result)) + { + throw new phpbb_db_migration_exception('MODULE_NOT_REMOVABLE', $module_id, $result); + } + } + + $this->cache->destroy("_modules_$class"); + } + } + + /** + * {@inheritdoc} + */ + public function reverse() + { + $arguments = func_get_args(); + $original_call = array_shift($arguments); + + $call = false; + switch ($original_call) + { + case 'add': + $call = 'remove'; + break; + + case 'remove': + $call = 'add'; + break; + } + + if ($call) + { + return call_user_func_array(array(&$this, $call), $arguments); + } + } +} diff --git a/phpBB/includes/db/migration/tool/permission.php b/phpBB/includes/db/migration/tool/permission.php new file mode 100644 index 0000000000..4231fbe1dd --- /dev/null +++ b/phpBB/includes/db/migration/tool/permission.php @@ -0,0 +1,622 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +/** +* Migration permission management tool +* +* @package db +*/ +class phpbb_db_migration_tool_permission implements phpbb_db_migration_tool_interface +{ + /** @var phpbb_auth */ + protected $auth; + + /** @var phpbb_cache_service */ + protected $cache; + + /** @var dbal */ + protected $db; + + /** @var string */ + protected $phpbb_root_path; + + /** @var string */ + protected $php_ext; + + /** + * Constructor + * + * @param phpbb_db_driver $db + * @param mixed $cache + * @param phpbb_auth $auth + * @param string $phpbb_root_path + * @param string $php_ext + */ + public function __construct(phpbb_db_driver $db, phpbb_cache_service $cache, phpbb_auth $auth, $phpbb_root_path, $php_ext) + { + $this->db = $db; + $this->cache = $cache; + $this->auth = $auth; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + } + + /** + * {@inheritdoc} + */ + public function get_name() + { + return 'permission'; + } + + /** + * Permission Exists + * + * Check if a permission (auth) setting exists + * + * @param string $auth_option The name of the permission (auth) option + * @param bool $global True for checking a global permission setting, + * False for a local permission setting + * @return bool true if it exists, false if not + */ + public function exists($auth_option, $global = true) + { + if ($global) + { + $type_sql = ' AND is_global = 1'; + } + else + { + $type_sql = ' AND is_local = 1'; + } + + $sql = 'SELECT auth_option_id + FROM ' . ACL_OPTIONS_TABLE . " + WHERE auth_option = '" . $this->db->sql_escape($auth_option) . "'" + . $type_sql; + $result = $this->db->sql_query($sql); + + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if ($row) + { + return true; + } + + return false; + } + + /** + * Permission Add + * + * Add a permission (auth) option + * + * @param string $auth_option The name of the permission (auth) option + * @param bool $global True for checking a global permission setting, + * False for a local permission setting + * @return null + */ + public function add($auth_option, $global = true, $copy_from = false) + { + if ($this->exists($auth_option, $global)) + { + throw new phpbb_db_migration_exception('PERMISSION_ALREADY_EXIST', $auth_option); + } + + // We've added permissions, so set to true to notify the user. + $this->permissions_added = true; + + if (!class_exists('auth_admin')) + { + include($this->phpbb_root_path . 'includes/acp/auth.' . $this->php_ext); + } + $auth_admin = new auth_admin(); + + // We have to add a check to see if the !$global (if global, local, and if local, global) permission already exists. If it does, acl_add_option currently has a bug which would break the ACL system, so we are having a work-around here. + if ($this->exists($auth_option, !$global)) + { + $sql_ary = array( + 'is_global' => 1, + 'is_local' => 1, + ); + $sql = 'UPDATE ' . ACL_OPTIONS_TABLE . ' + SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . " + WHERE auth_option = '" . $this->db->sql_escape($auth_option) . "'"; + $this->db->sql_query($sql); + } + else + { + if ($global) + { + $auth_admin->acl_add_option(array('global' => array($auth_option))); + } + else + { + $auth_admin->acl_add_option(array('local' => array($auth_option))); + } + } + + // The permission has been added, now we can copy it if needed + if ($copy_from && isset($auth_admin->acl_options['id'][$copy_from])) + { + $old_id = $auth_admin->acl_options['id'][$copy_from]; + $new_id = $auth_admin->acl_options['id'][$auth_option]; + + $tables = array(ACL_GROUPS_TABLE, ACL_ROLES_DATA_TABLE, ACL_USERS_TABLE); + + foreach ($tables as $table) + { + $sql = 'SELECT * + FROM ' . $table . ' + WHERE auth_option_id = ' . $old_id; + $result = $this->db->sql_query($sql); + + $sql_ary = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $row['auth_option_id'] = $new_id; + $sql_ary[] = $row; + } + $this->db->sql_freeresult($result); + + if (!empty($sql_ary)) + { + $this->db->sql_multi_insert($table, $sql_ary); + } + } + + $auth_admin->acl_clear_prefetch(); + } + } + + /** + * Permission Remove + * + * Remove a permission (auth) option + * + * @param string $auth_option The name of the permission (auth) option + * @param bool $global True for checking a global permission setting, + * False for a local permission setting + * @return null + */ + public function remove($auth_option, $global = true) + { + if (!$this->exists($auth_option, $global)) + { + throw new phpbb_db_migration_exception('PERMISSION_NOT_EXIST', $auth_option); + } + + if ($global) + { + $type_sql = ' AND is_global = 1'; + } + else + { + $type_sql = ' AND is_local = 1'; + } + $sql = 'SELECT auth_option_id, is_global, is_local + FROM ' . ACL_OPTIONS_TABLE . " + WHERE auth_option = '" . $this->db->sql_escape($auth_option) . "'" . + $type_sql; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + $id = (int) $row['auth_option_id']; + + // If it is a local and global permission, do not remove the row! :P + if ($row['is_global'] && $row['is_local']) + { + $sql = 'UPDATE ' . ACL_OPTIONS_TABLE . ' + SET ' . (($global) ? 'is_global = 0' : 'is_local = 0') . ' + WHERE auth_option_id = ' . $id; + $this->db->sql_query($sql); + } + else + { + // Delete time + $tables = array(ACL_GROUPS_TABLE, ACL_ROLES_DATA_TABLE, ACL_USERS_TABLE, ACL_OPTIONS_TABLE); + foreach ($tables as $table) + { + $this->db->sql_query('DELETE FROM ' . $table . ' + WHERE auth_option_id = ' . $id); + } + } + + // Purge the auth cache + $this->cache->destroy('_acl_options'); + $this->auth->acl_clear_prefetch(); + } + + /** + * Add a new permission role + * + * @param string $role_name The new role name + * @param sting $role_type The type (u_, m_, a_) + * @return null + */ + public function role_add($role_name, $role_type, $role_description = '') + { + $sql = 'SELECT role_id + FROM ' . ACL_ROLES_TABLE . " + WHERE role_name = '" . $this->db->sql_escape($role_name) . "'"; + $this->db->sql_query($sql); + $role_id = (int) $this->db->sql_fetchfield('role_id'); + + if ($role_id) + { + return; + } + + $sql = 'SELECT MAX(role_order) AS max_role_order + FROM ' . ACL_ROLES_TABLE . " + WHERE role_type = '" . $this->db->sql_escape($role_type) . "'"; + $this->db->sql_query($sql); + $role_order = (int) $this->db->sql_fetchfield('max_role_order'); + $role_order = (!$role_order) ? 1 : $role_order + 1; + + $sql_ary = array( + 'role_name' => $role_name, + 'role_description' => $role_description, + 'role_type' => $role_type, + 'role_order' => $role_order, + ); + + $sql = 'INSERT INTO ' . ACL_ROLES_TABLE . ' ' . $this->db->sql_build_array('INSERT', $sql_ary); + $this->db->sql_query($sql); + } + + /** + * Update the name on a permission role + * + * @param string $old_role_name The old role name + * @param string $new_role_name The new role name + * @return null + */ + public function role_update($old_role_name, $new_role_name) + { + $sql = 'SELECT role_id + FROM ' . ACL_ROLES_TABLE . " + WHERE role_name = '" . $this->db->sql_escape($old_role_name) . "'"; + $this->db->sql_query($sql); + $role_id = (int) $this->db->sql_fetchfield('role_id'); + + if (!$role_id) + { + throw new phpbb_db_migration_exception('ROLE_NOT_EXIST', $old_role_name); + } + + $sql = 'UPDATE ' . ACL_ROLES_TABLE . " + SET role_name = '" . $this->db->sql_escape($new_role_name) . "' + WHERE role_name = '" . $this->db->sql_escape($old_role_name) . "'"; + $this->db->sql_query($sql); + } + + /** + * Remove a permission role + * + * @param string $role_name The role name to remove + * @return null + */ + public function role_remove($role_name) + { + $sql = 'SELECT role_id + FROM ' . ACL_ROLES_TABLE . " + WHERE role_name = '" . $this->db->sql_escape($role_name) . "'"; + $this->db->sql_query($sql); + $role_id = (int) $this->db->sql_fetchfield('role_id'); + + if (!$role_id) + { + throw new phpbb_db_migration_exception('ROLE_NOT_EXIST', $role_name); + } + + $sql = 'DELETE FROM ' . ACL_ROLES_DATA_TABLE . ' + WHERE role_id = ' . $role_id; + $this->db->sql_query($sql); + + $sql = 'DELETE FROM ' . ACL_ROLES_TABLE . ' + WHERE role_id = ' . $role_id; + $this->db->sql_query($sql); + + $this->auth->acl_clear_prefetch(); + } + + /** + * Permission Set + * + * Allows you to set permissions for a certain group/role + * + * @param string $name The name of the role/group + * @param string|array $auth_option The auth_option or array of + * auth_options you would like to set + * @param string $type The type (role|group) + * @param bool $has_permission True if you want to give them permission, + * false if you want to deny them permission + * @return null + */ + public function permission_set($name, $auth_option, $type = 'role', $has_permission = true) + { + if (!is_array($auth_option)) + { + $auth_option = array($auth_option); + } + + $new_auth = array(); + $sql = 'SELECT auth_option_id + FROM ' . ACL_OPTIONS_TABLE . ' + WHERE ' . $this->db->sql_in_set('auth_option', $auth_option); + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $new_auth[] = (int) $row['auth_option_id']; + } + $this->db->sql_freeresult($result); + + if (empty($new_auth)) + { + return; + } + + $current_auth = array(); + + $type = (string) $type; // Prevent PHP bug. + + switch ($type) + { + case 'role': + $sql = 'SELECT role_id + FROM ' . ACL_ROLES_TABLE . " + WHERE role_name = '" . $this->db->sql_escape($name) . "'"; + $this->db->sql_query($sql); + $role_id = (int) $this->db->sql_fetchfield('role_id'); + + if (!$role_id) + { + throw new phpbb_db_migration_exception('ROLE_NOT_EXIST', $name); + } + + $sql = 'SELECT auth_option_id, auth_setting + FROM ' . ACL_ROLES_DATA_TABLE . ' + WHERE role_id = ' . $role_id; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $current_auth[$row['auth_option_id']] = $row['auth_setting']; + } + $this->db->sql_freeresult($result); + break; + + case 'group': + $sql = 'SELECT group_id + FROM ' . GROUPS_TABLE . " + WHERE group_name = '" . $this->db->sql_escape($name) . "'"; + $this->db->sql_query($sql); + $group_id = (int) $this->db->sql_fetchfield('group_id'); + + if (!$group_id) + { + throw new phpbb_db_migration_exception('GROUP_NOT_EXIST', $name); + } + + // If the group has a role set for them we will add the requested permissions to that role. + $sql = 'SELECT auth_role_id + FROM ' . ACL_GROUPS_TABLE . ' + WHERE group_id = ' . $group_id . ' + AND auth_role_id <> 0 + AND forum_id = 0'; + $this->db->sql_query($sql); + $role_id = (int) $this->db->sql_fetchfield('auth_role_id'); + if ($role_id) + { + $sql = 'SELECT role_name + FROM ' . ACL_ROLES_TABLE . ' + WHERE role_id = ' . $role_id; + $this->db->sql_query($sql); + $role_name = $this->db->sql_fetchfield('role_name'); + + return $this->set($role_name, $auth_option, 'role', $has_permission); + } + + $sql = 'SELECT auth_option_id, auth_setting + FROM ' . ACL_GROUPS_TABLE . ' + WHERE group_id = ' . $group_id; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $current_auth[$row['auth_option_id']] = $row['auth_setting']; + } + $this->db->sql_freeresult($result); + break; + } + + $sql_ary = array(); + switch ($type) + { + case 'role': + foreach ($new_auth as $auth_option_id) + { + if (!isset($current_auth[$auth_option_id])) + { + $sql_ary[] = array( + 'role_id' => $role_id, + 'auth_option_id' => $auth_option_id, + 'auth_setting' => $has_permission, + ); + } + } + + $this->db->sql_multi_insert(ACL_ROLES_DATA_TABLE, $sql_ary); + break; + + case 'group': + foreach ($new_auth as $auth_option_id) + { + if (!isset($current_auth[$auth_option_id])) + { + $sql_ary[] = array( + 'group_id' => $group_id, + 'auth_option_id' => $auth_option_id, + 'auth_setting' => $has_permission, + ); + } + } + + $this->db->sql_multi_insert(ACL_GROUPS_TABLE, $sql_ary); + break; + } + + $this->auth->acl_clear_prefetch(); + } + + /** + * Permission Unset + * + * Allows you to unset (remove) permissions for a certain group/role + * + * @param string $name The name of the role/group + * @param string|array $auth_option The auth_option or array of + * auth_options you would like to set + * @param string $type The type (role|group) + * @return null + */ + public function permission_unset($name, $auth_option, $type = 'role') + { + if (!is_array($auth_option)) + { + $auth_option = array($auth_option); + } + + $to_remove = array(); + $sql = 'SELECT auth_option_id + FROM ' . ACL_OPTIONS_TABLE . ' + WHERE ' . $this->db->sql_in_set('auth_option', $auth_option); + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $to_remove[] = (int) $row['auth_option_id']; + } + $this->db->sql_freeresult($result); + + if (empty($to_remove)) + { + return; + } + + $type = (string) $type; // Prevent PHP bug. + + switch ($type) + { + case 'role': + $sql = 'SELECT role_id + FROM ' . ACL_ROLES_TABLE . " + WHERE role_name = '" . $this->db->sql_escape($name) . "'"; + $this->db->sql_query($sql); + $role_id = (int) $this->db->sql_fetchfield('role_id'); + + if (!$role_id) + { + throw new phpbb_db_migration_exception('ROLE_NOT_EXIST', $name); + } + + $sql = 'DELETE FROM ' . ACL_ROLES_DATA_TABLE . ' + WHERE ' . $this->db->sql_in_set('auth_option_id', $to_remove); + $this->db->sql_query($sql); + break; + + case 'group': + $sql = 'SELECT group_id + FROM ' . GROUPS_TABLE . " + WHERE group_name = '" . $this->db->sql_escape($name) . "'"; + $this->db->sql_query($sql); + $group_id = (int) $this->db->sql_fetchfield('group_id'); + + if (!$group_id) + { + throw new phpbb_db_migration_exception('GROUP_NOT_EXIST', $name); + } + + // If the group has a role set for them we will remove the requested permissions from that role. + $sql = 'SELECT auth_role_id + FROM ' . ACL_GROUPS_TABLE . ' + WHERE group_id = ' . $group_id . ' + AND auth_role_id <> 0'; + $this->db->sql_query($sql); + $role_id = (int) $this->db->sql_fetchfield('auth_role_id'); + if ($role_id) + { + $sql = 'SELECT role_name + FROM ' . ACL_ROLES_TABLE . ' + WHERE role_id = ' . $role_id; + $this->db->sql_query($sql); + $role_name = $this->db->sql_fetchfield('role_name'); + + return $this->permission_unset($role_name, $auth_option, 'role'); + } + + $sql = 'DELETE FROM ' . ACL_GROUPS_TABLE . ' + WHERE ' . $this->db->sql_in_set('auth_option_id', $to_remove); + $this->db->sql_query($sql); + break; + } + + $this->auth->acl_clear_prefetch(); + } + + /** + * {@inheritdoc} + */ + public function reverse() + { + $arguments = func_get_args(); + $original_call = array_shift($arguments); + + $call = false; + switch ($original_call) + { + case 'add': + $call = 'remove'; + break; + + case 'remove': + $call = 'add'; + break; + + case 'permission_set': + $call = 'permission_unset'; + break; + + case 'permission_unset': + $call = 'permission_set'; + break; + + case 'role_add': + $call = 'role_remove'; + break; + + case 'role_remove': + $call = 'role_add'; + break; + + case 'role_update': + // Set to the original value if the current value is what we compared to originally + $arguments = array( + $arguments[1], + $arguments[0], + ); + break; + } + + if ($call) + { + return call_user_func_array(array(&$this, $call), $arguments); + } + } +} diff --git a/phpBB/includes/db/migrator.php b/phpBB/includes/db/migrator.php new file mode 100644 index 0000000000..41d996b1e3 --- /dev/null +++ b/phpBB/includes/db/migrator.php @@ -0,0 +1,764 @@ +<?php +/** +* +* @package db +* @copyright (c) 2011 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* The migrator is responsible for applying new migrations in the correct order. +* +* @package db +*/ +class phpbb_db_migrator +{ + /** @var phpbb_config */ + protected $config; + + /** @var phpbb_db_driver */ + protected $db; + + /** @var phpbb_db_tools */ + protected $db_tools; + + /** @var string */ + protected $table_prefix; + + /** @var string */ + protected $phpbb_root_path; + + /** @var string */ + protected $php_ext; + + /** @var string */ + protected $migrations_table; + + /** + * State of all migrations + * + * (SELECT * FROM migrations table) + * + * @var array + */ + protected $migration_state = array(); + + /** + * Array of all migrations available to be run + * + * @var array + */ + protected $migrations = array(); + + /** + * 'name' and 'class' of the last migration run + * + * @var array + */ + public $last_run_migration = false; + + /** + * Constructor of the database migrator + */ + public function __construct(phpbb_config $config, phpbb_db_driver $db, phpbb_db_tools $db_tools, $migrations_table, $phpbb_root_path, $php_ext, $table_prefix, $tools) + { + $this->config = $config; + $this->db = $db; + $this->db_tools = $db_tools; + + $this->migrations_table = $migrations_table; + + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + + $this->table_prefix = $table_prefix; + + foreach ($tools as $tool) + { + $this->tools[$tool->get_name()] = $tool; + } + + $this->load_migration_state(); + } + + /** + * Loads all migrations and their application state from the database. + * + * @return null + */ + public function load_migration_state() + { + $this->migration_state = array(); + + $sql = "SELECT * + FROM " . $this->migrations_table; + $result = $this->db->sql_query($sql); + + 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); + } + + /** + * Sets the list of available migration class names to the given array. + * + * @param array $class_names An array of migration class names + * @return null + */ + public function set_migrations($class_names) + { + $this->migrations = $class_names; + } + + /** + * This function adds all migrations in a specified directory to the migrations table + * + * THIS SHOULD NOT GENERALLY BE USED! THIS IS FOR THE PHPBB INSTALLER. + * THIS WILL THROW ERRORS IF MIGRATIONS ALREADY EXIST IN THE TABLE, DO NOT CALL MORE THAN ONCE! + * + * @param string $path Path to migration data files + * @param bool $recursive Set to true to also load data files from subdirectories + * @return null + */ + public function populate_migrations_from_directory($path, $recursive = true) + { + $existing_migrations = $this->migrations; + + $this->migrations = array(); + $this->load_migrations($path, true, $recursive); + + foreach ($this->migrations as $name) + { + if ($this->migration_state($name) === false) + { + $state = array( + 'migration_depends_on' => $name::depends_on(), + 'migration_schema_done' => true, + 'migration_data_done' => true, + 'migration_data_state' => '', + 'migration_start_time' => time(), + 'migration_end_time' => time(), + ); + $this->insert_migration($name, $state); + } + } + + $this->migrations = $existing_migrations; + } + + /** + * Load migration data files from a directory + * + * Migration data files loaded with this function MUST contain + * ONLY ONE class in them (or an exception will be thrown). + * + * @param string $path Path to migration data files + * @param bool $check_fulfillable If TRUE (default), we will check + * if all of the migrations are fulfillable after loading them. + * If FALSE, we will not check. You SHOULD check at least once + * to prevent errors (if including multiple directories, check + * with the last call to prevent throwing errors unnecessarily). + * @param bool $recursive Set to true to also load data files from subdirectories + * @return array Array of migration names + */ + public function load_migrations($path, $check_fulfillable = true, $recursive = true) + { + if (!is_dir($path)) + { + throw new phpbb_db_migration_exception('DIRECTORY INVALID', $path); + } + + $handle = opendir($path); + while (($file = readdir($handle)) !== false) + { + if ($file == '.' || $file == '..') + { + continue; + } + + // Recursion through subdirectories + if (is_dir($path . $file) && $recursive) + { + $this->load_migrations($path . $file . '/', $check_fulfillable, $recursive); + } + + if (strpos($file, '_') !== 0 && strrpos($file, '.' . $this->php_ext) === (strlen($file) - strlen($this->php_ext) - 1)) + { + // We try to find what class existed by comparing the classes declared before and after including the file. + $declared_classes = get_declared_classes(); + + include ($path . $file); + + $added_classes = array_diff(get_declared_classes(), $declared_classes); + + if ( + // If two classes have been added and phpbb_db_migration is one of them, we've only added one real migration + !(sizeof($added_classes) == 2 && in_array('phpbb_db_migration', $added_classes)) && + // Otherwise there should only be one class added + sizeof($added_classes) != 1 + ) + { + throw new phpbb_db_migration_exception('MIGRATION DATA FILE INVALID', $path . $file); + } + + $name = array_pop($added_classes); + + if (!in_array($name, $this->migrations)) + { + $this->migrations[] = $name; + } + } + } + + if ($check_fulfillable) + { + foreach ($this->migrations as $name) + { + $unfulfillable = $this->unfulfillable($name); + if ($unfulfillable !== false) + { + throw new phpbb_db_migration_exception('MIGRATION_NOT_FULFILLABLE', $name, $unfulfillable); + } + } + } + + return $this->migrations; + } + + /** + * Runs a single update step from the next migration to be applied. + * + * The update step can either be a schema or a (partial) data update. To + * check if update() needs to be called again use the finished() method. + * + * @return null + */ + public function update() + { + foreach ($this->migrations as $name) + { + if (!isset($this->migration_state[$name]) || + !$this->migration_state[$name]['migration_schema_done'] || + !$this->migration_state[$name]['migration_data_done']) + { + if (!$this->try_apply($name)) + { + continue; + } + else + { + return; + } + } + } + } + + /** + * Attempts to apply a step of the given migration or one of its dependencies + * + * @param string The class name of the migration + * @return bool Whether any update step was successfully run + */ + protected function try_apply($name) + { + if (!class_exists($name)) + { + return false; + } + + $migration = $this->get_migration($name); + + $state = (isset($this->migration_state[$name])) ? + $this->migration_state[$name] : + array( + 'migration_depends_on' => $migration->depends_on(), + 'migration_schema_done' => false, + 'migration_data_done' => false, + 'migration_data_state' => '', + 'migration_start_time' => 0, + 'migration_end_time' => 0, + ); + + foreach ($state['migration_depends_on'] as $depend) + { + if (!isset($this->migration_state[$depend]) || + !$this->migration_state[$depend]['migration_schema_done'] || + !$this->migration_state[$depend]['migration_data_done']) + { + return $this->try_apply($depend); + } + } + + $this->last_run_migration = array( + 'name' => $name, + 'class' => $migration, + ); + + if ($migration->effectively_installed()) + { + $state = array( + 'migration_depends_on' => $migration->depends_on(), + 'migration_schema_done' => true, + 'migration_data_done' => true, + 'migration_data_state' => '', + 'migration_start_time' => 0, + 'migration_end_time' => 0, + ); + } + else + { + if (!isset($this->migration_state[$name])) + { + $state['migration_start_time'] = time(); + $this->insert_migration($name, $state); + } + } + + 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; + } + } + + $insert = $state; + $insert['migration_depends_on'] = serialize($state['migration_depends_on']); + $sql = 'UPDATE ' . $this->migrations_table . ' + SET ' . $this->db->sql_build_array('UPDATE', $insert) . " + WHERE migration_name = '" . $this->db->sql_escape($name) . "'"; + $this->db->sql_query($sql); + + $this->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(), $state['migration_data_state'], false); + + $state['migration_data_state'] = ($result === true) ? '' : $result; + $state['migration_data_done'] = ($result === true) ? false : true; + } + + $insert = $state; + $insert['migration_depends_on'] = serialize($state['migration_depends_on']); + $sql = 'UPDATE ' . $this->migrations_table . ' + SET ' . $this->db->sql_build_array('UPDATE', $insert) . " + WHERE migration_name = '" . $this->db->sql_escape($name) . "'"; + $this->db->sql_query($sql); + + $this->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; + + 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 migration row into the database + * + * @param string $name Name of the migration + * @param array $state + * @return null + */ + protected function insert_migration($name, $state) + { + $migration_row = $state; + $migration_row['migration_name'] = $name; + $migration_row['migration_depends_on'] = serialize($state['migration_depends_on']); + + $sql = 'INSERT INTO ' . $this->migrations_table . ' + ' . $this->db->sql_build_array('INSERT', $migration_row); + $this->db->sql_query($sql); + + $this->migration_state[$name] = $state; + } + + /** + * Checks if a migration's dependencies can even theoretically be satisfied. + * + * @param string $name The class name of the migration + * @return bool|string False if fulfillable, string of missing migration name if unfulfillable + */ + public function unfulfillable($name) + { + if (isset($this->migration_state[$name])) + { + return false; + } + + if (!class_exists($name)) + { + return $name; + } + + $migration = $this->get_migration($name); + $depends = $migration->depends_on(); + + foreach ($depends as $depend) + { + $unfulfillable = $this->unfulfillable($depend); + if ($unfulfillable !== false) + { + return $unfulfillable; + } + } + + return false; + } + + /** + * Checks whether all available, fulfillable migrations have been applied. + * + * @return bool Whether the migrations have been applied + */ + public function finished() + { + foreach ($this->migrations as $name) + { + if (!isset($this->migration_state[$name])) + { + // skip unfulfillable migrations, but fulfillables mean we + // are not finished yet + if ($this->unfulfillable($name) !== false) + { + continue; + } + return false; + } + + $migration = $this->migration_state[$name]; + + if (!$migration['migration_schema_done'] || !$migration['migration_data_done']) + { + return false; + } + } + + return true; + } + + /** + * Gets a migration state (whether it is installed and to what extent) + * + * @param string $migration String migration name to check if it is installed + * @return bool|array False if the migration has not at all been installed, array + */ + public function migration_state($migration) + { + if (!isset($this->migration_state[$migration])) + { + return false; + } + + return $this->migration_state[$migration]; + } + + /** + * Helper to get a migration + * + * @param string $name Name of the migration + * @return phpbb_db_migration + */ + protected function get_migration($name) + { + return new $name($this->config, $this->db, $this->db_tools, $this->phpbb_root_path, $this->php_ext, $this->table_prefix); + } +} diff --git a/phpBB/includes/extension/base.php b/phpBB/includes/extension/base.php index 9d076eb6c5..d51589d719 100644 --- a/phpBB/includes/extension/base.php +++ b/phpBB/includes/extension/base.php @@ -15,6 +15,8 @@ if (!defined('IN_PHPBB')) exit; } +use Symfony\Component\DependencyInjection\ContainerInterface; + /** * A base class for extensions without custom enable/disable/purge code. * @@ -22,6 +24,19 @@ if (!defined('IN_PHPBB')) */ class phpbb_extension_base implements phpbb_extension_interface { + /** @var ContainerInterface */ + protected $container; + + /** + * Constructor + * + * @param ContainerInterface $container Container object + */ + public function __construct(ContainerInterface $container) + { + $this->container = $container; + } + /** * Single enable step that does nothing * diff --git a/phpBB/includes/extension/manager.php b/phpBB/includes/extension/manager.php index de6f364320..21a9ec1370 100644 --- a/phpBB/includes/extension/manager.php +++ b/phpBB/includes/extension/manager.php @@ -15,6 +15,8 @@ if (!defined('IN_PHPBB')) exit; } +use Symfony\Component\DependencyInjection\ContainerInterface; + /** * The extension manager provides means to activate/deactivate extensions. * @@ -22,8 +24,12 @@ if (!defined('IN_PHPBB')) */ class phpbb_extension_manager { + /** @var ContainerInterface */ + protected $container; + protected $db; protected $config; + protected $migrator; protected $cache; protected $php_ext; protected $extensions; @@ -34,6 +40,7 @@ class phpbb_extension_manager /** * Creates a manager and loads information from database * + * @param ContainerInterface $container A container * @param phpbb_db_driver $db A database connection * @param phpbb_config $config phpbb_config * @param string $extension_table The name of the table holding extensions @@ -42,11 +49,13 @@ class phpbb_extension_manager * @param phpbb_cache_driver_interface $cache A cache instance or null * @param string $cache_name The name of the cache variable, defaults to _ext */ - public function __construct(phpbb_db_driver $db, phpbb_config $config, $extension_table, $phpbb_root_path, $php_ext = '.php', phpbb_cache_driver_interface $cache = null, $cache_name = '_ext') + public function __construct(ContainerInterface $container, phpbb_db_driver $db, phpbb_config $config, phpbb_db_migrator $migrator, $extension_table, $phpbb_root_path, $php_ext = '.php', phpbb_cache_driver_interface $cache = null, $cache_name = '_ext') { + $this->container = $container; $this->phpbb_root_path = $phpbb_root_path; $this->db = $db; $this->config = $config; + $this->migrator = $migrator; $this->cache = $cache; $this->php_ext = $php_ext; $this->extension_table = $extension_table; @@ -126,11 +135,11 @@ class phpbb_extension_manager if (class_exists($extension_class_name)) { - return new $extension_class_name; + return new $extension_class_name($this->container); } else { - return new phpbb_extension_base; + return new phpbb_extension_base($this->container); } } @@ -166,6 +175,12 @@ class phpbb_extension_manager $old_state = (isset($this->extensions[$name]['ext_state'])) ? unserialize($this->extensions[$name]['ext_state']) : false; + // Returns false if not completed + if (!$this->handle_migrations($name, 'enable')) + { + return true; + } + $extension = $this->get_extension($name); $state = $extension->enable_step($old_state); @@ -317,6 +332,12 @@ class phpbb_extension_manager $old_state = unserialize($this->extensions[$name]['ext_state']); + // Returns false if not completed + if (!$this->handle_migrations($name, 'purge')) + { + return true; + } + $extension = $this->get_extension($name); $state = $extension->purge_step($old_state); @@ -490,4 +511,58 @@ class phpbb_extension_manager { return new phpbb_extension_finder($this, $this->phpbb_root_path, $this->cache, $this->php_ext, $this->cache_name . '_finder'); } + + /** + * Handle installing/reverting migrations + * + * @param string $extension_name Name of the extension + * @param string $mode enable or purge + * @return bool True if completed, False if not completed + */ + protected function handle_migrations($extension_name, $mode) + { + $migrations_path = $this->phpbb_root_path . $this->get_extension_path($extension_name) . 'migrations/'; + if (!file_exists($migrations_path) || !is_dir($migrations_path)) + { + return true; + } + + $migrations = $this->migrator->load_migrations($migrations_path); + + // What is a safe limit of execution time? Half the max execution time should be safe. + $safe_time_limit = (ini_get('max_execution_time') / 2); + $start_time = time(); + + if ($mode == 'enable') + { + while (!$this->migrator->finished()) + { + $this->migrator->update(); + + // Are we approaching the time limit? If so we want to pause the update and continue after refreshing + if ((time() - $start_time) >= $safe_time_limit) + { + return false; + } + } + } + else if ($mode == 'purge') + { + foreach ($migrations as $migration) + { + while ($this->migrator->migration_state($migration) !== false) + { + $this->migrator->revert($migration); + + // Are we approaching the time limit? If so we want to pause the update and continue after refreshing + if ((time() - $start_time) >= $safe_time_limit) + { + return false; + } + } + } + } + + return true; + } } diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index d0ef2759d5..d9af268f80 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -97,7 +97,18 @@ function request_var($var_name, $default, $multibyte = false, $cookie = false, $ } /** -* Set config value. Creates missing config entry. +* Sets a configuration option's value. +* +* Please note that this function does not update the is_dynamic value for +* an already existing config option. +* +* @param string $config_name The configuration option's name +* @param string $config_value New configuration value +* @param bool $is_dynamic Whether this variable should be cached (false) or +* if it changes too frequently (true) to be +* efficiently cached. +* +* @return null * * @deprecated */ @@ -119,7 +130,15 @@ function set_config($config_name, $config_value, $is_dynamic = false, phpbb_conf } /** -* Set dynamic config value with arithmetic operation. +* Increments an integer config value directly in the database. +* +* @param string $config_name The configuration option's name +* @param int $increment Amount to increment by +* @param bool $is_dynamic Whether this variable should be cached (false) or +* if it changes too frequently (true) to be +* efficiently cached. +* +* @return null * * @deprecated */ @@ -5582,7 +5601,7 @@ function phpbb_convert_30_dbms_to_31($dbms) /* $reflection = new \ReflectionClass($dbms); - + if ($reflection->isSubclassOf('phpbb_db_driver')) { return $dbms; diff --git a/phpBB/includes/functions_acp.php b/phpBB/includes/functions_acp.php index 32fd76e74d..d6bd9e35dd 100644 --- a/phpBB/includes/functions_acp.php +++ b/phpBB/includes/functions_acp.php @@ -443,6 +443,13 @@ function validate_config_vars($config_vars, &$cfg_array, &$error) } break; + case 'email': + if (!preg_match('/^' . get_preg_expression('email') . '$/i', $cfg_array[$config_name])) + { + $error[] = $user->lang['EMAIL_INVALID_EMAIL']; + } + break; + // Absolute path case 'script_path': if (!$cfg_array[$config_name]) diff --git a/phpBB/includes/functions_messenger.php b/phpBB/includes/functions_messenger.php index d0a02567ad..821f0d970d 100644 --- a/phpBB/includes/functions_messenger.php +++ b/phpBB/includes/functions_messenger.php @@ -393,6 +393,28 @@ class messenger } /** + * Generates a valid message id to be used in emails + * + * @return string message id + */ + function generate_message_id() + { + global $config; + + $domain = 'phpbb.generated'; + if ($config['server_name']) + { + $domain = $config['server_name']; + } + else if (!empty($_SERVER['SERVER_NAME'])) + { + $domain = $_SERVER['SERVER_NAME']; + } + + return md5(unique_id(time())) . '@' . $domain; + } + + /** * Return email header */ function build_header($to, $cc, $bcc) @@ -418,7 +440,7 @@ class messenger $headers[] = 'Return-Path: <' . $config['board_email'] . '>'; $headers[] = 'Sender: <' . $config['board_email'] . '>'; $headers[] = 'MIME-Version: 1.0'; - $headers[] = 'Message-ID: <' . md5(unique_id(time())) . '@' . $config['server_name'] . '>'; + $headers[] = 'Message-ID: <' . $this->generate_message_id() . '>'; $headers[] = 'Date: ' . date('r', time()); $headers[] = 'Content-Type: text/plain; charset=UTF-8'; // format=flowed $headers[] = 'Content-Transfer-Encoding: 8bit'; // 7bit diff --git a/phpBB/includes/search/base.php b/phpBB/includes/search/base.php index b364dead9a..914cef9167 100644 --- a/phpBB/includes/search/base.php +++ b/phpBB/includes/search/base.php @@ -94,7 +94,7 @@ class phpbb_search_base * * @return int SEARCH_RESULT_NOT_IN_CACHE or SEARCH_RESULT_IN_CACHE or SEARCH_RESULT_INCOMPLETE */ - function obtain_ids($search_key, &$result_count, &$id_ary, $start, $per_page, $sort_dir) + function obtain_ids($search_key, &$result_count, &$id_ary, &$start, $per_page, $sort_dir) { global $cache; @@ -109,6 +109,19 @@ class phpbb_search_base $reverse_ids = ($stored_ids[-2] != $sort_dir) ? true : false; $complete = true; + // Change start parameter in case out of bounds + if ($result_count) + { + if ($start < 0) + { + $start = 0; + } + else if ($start >= $result_count) + { + $start = floor(($result_count - 1) / $per_page) * $per_page; + } + } + // change the start to the actual end of the current request if the sort direction differs // from the dirction in the cache and reverse the ids later if ($reverse_ids) diff --git a/phpBB/includes/search/fulltext_mysql.php b/phpBB/includes/search/fulltext_mysql.php index 324c214e91..adaf025730 100644 --- a/phpBB/includes/search/fulltext_mysql.php +++ b/phpBB/includes/search/fulltext_mysql.php @@ -353,7 +353,7 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base * @param int $per_page number of ids each page is supposed to contain * @return boolean|int total number of results */ - public function keyword_search($type, $fields, $terms, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $m_approve_fid_ary, $topic_id, $author_ary, $author_name, &$id_ary, $start, $per_page) + public function keyword_search($type, $fields, $terms, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $m_approve_fid_ary, $topic_id, $author_ary, $author_name, &$id_ary, &$start, $per_page) { // No keywords? No posts if (!$this->search_query) @@ -375,6 +375,11 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base implode(',', $author_ary) ))); + if ($start < 0) + { + $start = 0; + } + // try reading the results from cache $result_count = 0; if ($this->obtain_ids($search_key, $result_count, $id_ary, $start, $per_page, $sort_dir) == SEARCH_RESULT_IN_CACHE) @@ -488,16 +493,11 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base $id_ary = array_unique($id_ary); - if (!sizeof($id_ary)) - { - return false; - } - // if the total result count is not cached yet, retrieve it from the db if (!$result_count) { - $sql = 'SELECT FOUND_ROWS() as result_count'; - $result = $this->db->sql_query($sql); + $sql_found_rows = 'SELECT FOUND_ROWS() as result_count'; + $result = $this->db->sql_query($sql_found_rows); $result_count = (int) $this->db->sql_fetchfield('result_count'); $this->db->sql_freeresult($result); @@ -507,6 +507,21 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base } } + if ($start >= $result_count) + { + $start = floor(($result_count - 1) / $per_page) * $per_page; + + $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); + + while ($row = $this->db->sql_fetchrow($result)) + { + $id_ary[] = (int) $row[$field]; + } + $this->db->sql_freeresult($result); + + $id_ary = array_unique($id_ary); + } + // store the ids, from start on then delete anything that isn't on the current page because we only need ids for one page $this->save_ids($search_key, implode(' ', $this->split_words), $author_ary, $result_count, $id_ary, $start, $sort_dir); $id_ary = array_slice($id_ary, 0, (int) $per_page); @@ -533,7 +548,7 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base * @param int $per_page number of ids each page is supposed to contain * @return boolean|int total number of results */ - public function author_search($type, $firstpost_only, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $m_approve_fid_ary, $topic_id, $author_ary, $author_name, &$id_ary, $start, $per_page) + public function author_search($type, $firstpost_only, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $m_approve_fid_ary, $topic_id, $author_ary, $author_name, &$id_ary, &$start, $per_page) { // No author? No posts if (!sizeof($author_ary)) @@ -557,6 +572,11 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base $author_name, ))); + if ($start < 0) + { + $start = 0; + } + // try reading the results from cache $result_count = 0; if ($this->obtain_ids($search_key, $result_count, $id_ary, $start, $per_page, $sort_dir) == SEARCH_RESULT_IN_CACHE) @@ -662,8 +682,8 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base // retrieve the total result count if needed if (!$result_count) { - $sql = 'SELECT FOUND_ROWS() as result_count'; - $result = $this->db->sql_query($sql); + $sql_found_rows = 'SELECT FOUND_ROWS() as result_count'; + $result = $this->db->sql_query($sql_found_rows); $result_count = (int) $this->db->sql_fetchfield('result_count'); $this->db->sql_freeresult($result); @@ -673,6 +693,20 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base } } + if ($start >= $result_count) + { + $start = floor(($result_count - 1) / $per_page) * $per_page; + + $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); + while ($row = $this->db->sql_fetchrow($result)) + { + $id_ary[] = (int) $row[$field]; + } + $this->db->sql_freeresult($result); + + $id_ary = array_unique($id_ary); + } + if (sizeof($id_ary)) { $this->save_ids($search_key, '', $author_ary, $result_count, $id_ary, $start, $sort_dir); diff --git a/phpBB/includes/search/fulltext_native.php b/phpBB/includes/search/fulltext_native.php index 53df8348ae..c9f33054fc 100644 --- a/phpBB/includes/search/fulltext_native.php +++ b/phpBB/includes/search/fulltext_native.php @@ -516,7 +516,7 @@ class phpbb_search_fulltext_native extends phpbb_search_base * @param int $per_page number of ids each page is supposed to contain * @return boolean|int total number of results */ - public function keyword_search($type, $fields, $terms, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $m_approve_fid_ary, $topic_id, $author_ary, $author_name, &$id_ary, $start, $per_page) + public function keyword_search($type, $fields, $terms, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $m_approve_fid_ary, $topic_id, $author_ary, $author_name, &$id_ary, &$start, $per_page) { // No keywords? No posts. if (empty($this->search_query)) @@ -855,10 +855,6 @@ class phpbb_search_fulltext_native extends phpbb_search_base } $this->db->sql_freeresult($result); - if (!sizeof($id_ary)) - { - return false; - } // if we use mysql and the total result count is not cached yet, retrieve it from the db if (!$total_results && $is_mysql) @@ -867,14 +863,14 @@ class phpbb_search_fulltext_native extends phpbb_search_base $sql_array_copy = $sql_array; $sql_array_copy['SELECT'] = 'SQL_CALC_FOUND_ROWS p.post_id '; - $sql = $this->db->sql_build_query('SELECT', $sql_array_copy); + $sql_calc = $this->db->sql_build_query('SELECT', $sql_array_copy); unset($sql_array_copy); - $this->db->sql_query($sql); + $this->db->sql_query($sql_calc); $this->db->sql_freeresult($result); - $sql = 'SELECT FOUND_ROWS() as total_results'; - $result = $this->db->sql_query($sql); + $sql_count = 'SELECT FOUND_ROWS() as total_results'; + $result = $this->db->sql_query($sql_count); $total_results = (int) $this->db->sql_fetchfield('total_results'); $this->db->sql_freeresult($result); @@ -884,6 +880,20 @@ class phpbb_search_fulltext_native extends phpbb_search_base } } + if ($start >= $total_results) + { + $start = floor(($total_results - 1) / $per_page) * $per_page; + + $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); + + while ($row = $this->db->sql_fetchrow($result)) + { + $id_ary[] = (int) $row[(($type == 'posts') ? 'post_id' : 'topic_id')]; + } + $this->db->sql_freeresult($result); + + } + // store the ids, from start on then delete anything that isn't on the current page because we only need ids for one page $this->save_ids($search_key, $this->search_query, $author_ary, $total_results, $id_ary, $start, $sort_dir); $id_ary = array_slice($id_ary, 0, (int) $per_page); @@ -910,7 +920,7 @@ class phpbb_search_fulltext_native extends phpbb_search_base * @param int $per_page number of ids each page is supposed to contain * @return boolean|int total number of results */ - public function author_search($type, $firstpost_only, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $m_approve_fid_ary, $topic_id, $author_ary, $author_name, &$id_ary, $start, $per_page) + public function author_search($type, $firstpost_only, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $m_approve_fid_ary, $topic_id, $author_ary, $author_name, &$id_ary, &$start, $per_page) { // No author? No posts if (!sizeof($author_ary)) @@ -1096,13 +1106,13 @@ class phpbb_search_fulltext_native extends phpbb_search_base if (!$total_results && $is_mysql) { // Count rows for the executed queries. Replace $select within $sql with SQL_CALC_FOUND_ROWS, and run it. - $sql = str_replace('SELECT ' . $select, 'SELECT DISTINCT SQL_CALC_FOUND_ROWS p.post_id', $sql); + $sql_calc = str_replace('SELECT ' . $select, 'SELECT DISTINCT SQL_CALC_FOUND_ROWS p.post_id', $sql); - $this->db->sql_query($sql); + $this->db->sql_query($sql_calc); $this->db->sql_freeresult($result); - $sql = 'SELECT FOUND_ROWS() as total_results'; - $result = $this->db->sql_query($sql); + $sql_count = 'SELECT FOUND_ROWS() as total_results'; + $result = $this->db->sql_query($sql_count); $total_results = (int) $this->db->sql_fetchfield('total_results'); $this->db->sql_freeresult($result); @@ -1112,6 +1122,19 @@ class phpbb_search_fulltext_native extends phpbb_search_base } } + if ($start >= $total_results) + { + $start = floor(($total_results - 1) / $per_page) * $per_page; + + $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); + + while ($row = $this->db->sql_fetchrow($result)) + { + $id_ary[] = (int) $row[$field]; + } + $this->db->sql_freeresult($result); + } + if (sizeof($id_ary)) { $this->save_ids($search_key, '', $author_ary, $total_results, $id_ary, $start, $sort_dir); diff --git a/phpBB/includes/search/fulltext_postgres.php b/phpBB/includes/search/fulltext_postgres.php index 1475cc31d0..eeb628b18f 100644 --- a/phpBB/includes/search/fulltext_postgres.php +++ b/phpBB/includes/search/fulltext_postgres.php @@ -343,7 +343,7 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base * @param int $per_page number of ids each page is supposed to contain * @return boolean|int total number of results */ - public function keyword_search($type, $fields, $terms, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $m_approve_fid_ary, $topic_id, $author_ary, $author_name, &$id_ary, $start, $per_page) + public function keyword_search($type, $fields, $terms, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $m_approve_fid_ary, $topic_id, $author_ary, $author_name, &$id_ary, &$start, $per_page) { // No keywords? No posts if (!$this->search_query) @@ -371,6 +371,11 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base implode(',', $author_ary) ))); + if ($start < 0) + { + $start = 0; + } + // try reading the results from cache $result_count = 0; if ($this->obtain_ids($search_key, $result_count, $id_ary, $start, $per_page, $sort_dir) == SEARCH_RESULT_IN_CACHE) @@ -495,11 +500,6 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base $id_ary = array_unique($id_ary); - if (!sizeof($id_ary)) - { - return false; - } - // if the total result count is not cached yet, retrieve it from the db if (!$result_count) { @@ -518,6 +518,21 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base $this->db->sql_transaction('commit'); + if ($start >= $result_count) + { + $start = floor(($result_count - 1) / $per_page) * $per_page; + + $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); + + while ($row = $this->db->sql_fetchrow($result)) + { + $id_ary[] = $row[$field]; + } + $this->db->sql_freeresult($result); + + $id_ary = array_unique($id_ary); + } + // store the ids, from start on then delete anything that isn't on the current page because we only need ids for one page $this->save_ids($search_key, implode(' ', $this->split_words), $author_ary, $result_count, $id_ary, $start, $sort_dir); $id_ary = array_slice($id_ary, 0, (int) $per_page); @@ -544,7 +559,7 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base * @param int $per_page number of ids each page is supposed to contain * @return boolean|int total number of results */ - public function author_search($type, $firstpost_only, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $m_approve_fid_ary, $topic_id, $author_ary, $author_name, &$id_ary, $start, $per_page) + public function author_search($type, $firstpost_only, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $m_approve_fid_ary, $topic_id, $author_ary, $author_name, &$id_ary, &$start, $per_page) { // No author? No posts if (!sizeof($author_ary)) @@ -568,6 +583,11 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base $author_name, ))); + if ($start < 0) + { + $start = 0; + } + // try reading the results from cache $result_count = 0; if ($this->obtain_ids($search_key, $result_count, $id_ary, $start, $per_page, $sort_dir) == SEARCH_RESULT_IN_CACHE) @@ -710,6 +730,20 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base $this->db->sql_transaction('commit'); + if ($start >= $result_count) + { + $start = floor(($result_count - 1) / $per_page) * $per_page; + + $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); + while ($row = $this->db->sql_fetchrow($result)) + { + $id_ary[] = (int) $row[$field]; + } + $this->db->sql_freeresult($result); + + $id_ary = array_unique($id_ary); + } + if (sizeof($id_ary)) { $this->save_ids($search_key, '', $author_ary, $result_count, $id_ary, $start, $sort_dir); diff --git a/phpBB/includes/search/fulltext_sphinx.php b/phpBB/includes/search/fulltext_sphinx.php index 4bacf74f93..48445d0794 100644 --- a/phpBB/includes/search/fulltext_sphinx.php +++ b/phpBB/includes/search/fulltext_sphinx.php @@ -454,7 +454,7 @@ class phpbb_search_fulltext_sphinx * @param int $per_page number of ids each page is supposed to contain * @return boolean|int total number of results */ - public function keyword_search($type, $fields, $terms, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $m_approve_fid_ary, $topic_id, $author_ary, $author_name, &$id_ary, $start, $per_page) + public function keyword_search($type, $fields, $terms, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $m_approve_fid_ary, $topic_id, $author_ary, $author_name, &$id_ary, &$start, $per_page) { // No keywords? No posts. if (!strlen($this->search_query) && !sizeof($author_ary)) @@ -609,6 +609,25 @@ class phpbb_search_fulltext_sphinx } } + $result_count = $result['total_found']; + + if ($start >= $result_count) + { + $start = floor(($result_count - 1) / $per_page) * $per_page; + + $this->sphinx->SetLimits((int) $start, (int) $per_page, SPHINX_MAX_MATCHES); + $result = $this->sphinx->Query($search_query_prefix . str_replace('"', '"', $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'])) { @@ -629,8 +648,6 @@ class phpbb_search_fulltext_sphinx return false; } - $result_count = $result['total_found']; - $id_ary = array_slice($id_ary, 0, (int) $per_page); return $result_count; @@ -878,8 +895,8 @@ class phpbb_search_fulltext_sphinx <dd><input id="fulltext_sphinx_indexer_mem_limit" type="text" size="4" maxlength="10" name="config[fulltext_sphinx_indexer_mem_limit]" value="' . $this->config['fulltext_sphinx_indexer_mem_limit'] . '" /> ' . $this->user->lang['MIB'] . '</dd> </dl> <dl> - <dt><label for="fulltext_sphinx_config_file">' . $this->user->lang['FULLTEXT_SPHINX_CONFIG_FILE'] . $this->user->lang['COLON'] . '</label><br /><span>' . $this->user->lang['FULLTEXT_SPHINX_CONFIG_FILE_EXPLAIN'] . '</dt> - <dd>' . (($this->config_generate()) ? '<textarea readonly="readonly" rows="6">' . $this->config_file_data . '</textarea>' : $this->config_file_data) . '</dd> + <dt><label for="fulltext_sphinx_config_file">' . $this->user->lang['FULLTEXT_SPHINX_CONFIG_FILE'] . $this->user->lang['COLON'] . '</label><br /><span>' . $this->user->lang['FULLTEXT_SPHINX_CONFIG_FILE_EXPLAIN'] . '</span></dt> + <dd>' . (($this->config_generate()) ? '<textarea readonly="readonly" rows="6" id="sphinx_config_data">' . htmlspecialchars($this->config_file_data) . '</textarea>' : $this->config_file_data) . '</dd> <dl> '; diff --git a/phpBB/includes/session.php b/phpBB/includes/session.php index ee8a4094c7..6bc71da0c1 100644 --- a/phpBB/includes/session.php +++ b/phpBB/includes/session.php @@ -346,7 +346,7 @@ class phpbb_session $session_id = $request->variable('sid', ''); if (defined('NEED_SID') && (empty($session_id) || $this->session_id !== $session_id)) { - send_status_line(401, 'Not authorized'); + send_status_line(401, 'Unauthorized'); redirect(append_sid("{$phpbb_root_path}index.$phpEx")); } diff --git a/phpBB/includes/ucp/ucp_pm_viewmessage.php b/phpBB/includes/ucp/ucp_pm_viewmessage.php index a1001cfa74..c85b05f144 100644 --- a/phpBB/includes/ucp/ucp_pm_viewmessage.php +++ b/phpBB/includes/ucp/ucp_pm_viewmessage.php @@ -241,6 +241,7 @@ function view_message($id, $mode, $folder_id, $msg_id, $folder, $message_row) 'U_ICQ' => ($user_info['user_icq']) ? 'http://www.icq.com/people/' . urlencode($user_info['user_icq']) . '/' : '', 'U_AIM' => ($user_info['user_aim'] && $auth->acl_get('u_sendim')) ? append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=contact&action=aim&u=' . $author_id) : '', 'U_YIM' => ($user_info['user_yim']) ? 'http://edit.yahoo.com/config/send_webmesg?.target=' . urlencode($user_info['user_yim']) . '&.src=pg' : '', + 'U_MSN' => ($user_info['user_msnm'] && $auth->acl_get('u_sendim')) ? append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=contact&action=msnm&u=' . $author_id) : '', 'U_JABBER' => ($user_info['user_jabber'] && $auth->acl_get('u_sendim')) ? append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=contact&action=jabber&u=' . $author_id) : '', 'U_DELETE' => ($auth->acl_get('u_pm_delete')) ? "$url&mode=compose&action=delete&f=$folder_id&p=" . $message_row['msg_id'] : '', diff --git a/phpBB/includes/ucp/ucp_profile.php b/phpBB/includes/ucp/ucp_profile.php index c1ad9955b6..e7cea06a45 100644 --- a/phpBB/includes/ucp/ucp_profile.php +++ b/phpBB/includes/ucp/ucp_profile.php @@ -266,6 +266,7 @@ class ucp_profile $data = array( 'icq' => request_var('icq', $user->data['user_icq']), 'aim' => request_var('aim', $user->data['user_aim']), + 'msn' => request_var('msn', $user->data['user_msnm']), 'yim' => request_var('yim', $user->data['user_yim']), 'jabber' => utf8_normalize_nfc(request_var('jabber', $user->data['user_jabber'], true)), 'website' => request_var('website', $user->data['user_website']), @@ -298,6 +299,7 @@ class ucp_profile array('string', true, 3, 15), array('match', true, '#^[0-9]+$#i')), 'aim' => array('string', true, 3, 255), + 'msn' => array('string', true, 5, 255), 'jabber' => array( array('string', true, 5, 255), array('jabber')), @@ -349,6 +351,7 @@ class ucp_profile $sql_ary = array( 'user_icq' => $data['icq'], 'user_aim' => $data['aim'], + 'user_msnm' => $data['msn'], 'user_yim' => $data['yim'], 'user_jabber' => $data['jabber'], 'user_website' => $data['website'], @@ -420,6 +423,7 @@ class ucp_profile 'ICQ' => $data['icq'], 'YIM' => $data['yim'], 'AIM' => $data['aim'], + 'MSN' => $data['msn'], 'JABBER' => $data['jabber'], 'WEBSITE' => $data['website'], 'LOCATION' => $data['location'], |