aboutsummaryrefslogtreecommitdiffstats
path: root/phpBB/includes/db
diff options
context:
space:
mode:
authorNathaniel Guse <nathaniel.guse@gmail.com>2013-02-11 21:37:15 -0600
committerNathaniel Guse <nathaniel.guse@gmail.com>2013-02-11 21:37:15 -0600
commit54e9f7b50ab8b25f63945c6ff09cc9ffa80c04af (patch)
tree09adec7705026866584002de8cb7f760581e876b /phpBB/includes/db
parente4c37c159ab4eb152280dec8e46c9d98a26354a0 (diff)
parentfa33eae556c248ef6b2d41d9c9203b29e23dfb3a (diff)
downloadforums-54e9f7b50ab8b25f63945c6ff09cc9ffa80c04af.tar
forums-54e9f7b50ab8b25f63945c6ff09cc9ffa80c04af.tar.gz
forums-54e9f7b50ab8b25f63945c6ff09cc9ffa80c04af.tar.bz2
forums-54e9f7b50ab8b25f63945c6ff09cc9ffa80c04af.tar.xz
forums-54e9f7b50ab8b25f63945c6ff09cc9ffa80c04af.zip
Merge branch 'develop' of https://github.com/phpbb/phpbb3 into ticket/11103
# By Nathan Guse (28) and others # Via Andreas Fischer (9) and others * 'develop' of https://github.com/phpbb/phpbb3: (90 commits) [ticket/11350] Do not pass $db by reference; typehint phpbb_db_driver [feature/migrations] Remove default values from necessary parameters [ticket/11201] Revert WLM dropping because it is still used in China. [ticket/11220] Improvement to the info pop-up from "list=" [feature/migrations] Revert unrelated changes to functions.php [ticket/11233] prohibit selecting anonymous user as a PM recipient [ticket/11343] Remove spare parentheses. [ticket/11343] Remove spare space. [ticket/11343] Use === when checking stored user_actkey against user input. [ticket/11295] Correct cases: replace postgres with phpbb_db_driver_postgres. [ticket/10050] removing prosilver edits [ticket/9737] Fix some comments [ticket/11337] Abort setup-webserver.sh script when an error occurs. [ticket/11337] Only run functional tests on 5.3.19 or higher. No FPM otherwise. [ticket/11337] Silence nginx config file writing. [ticket/11337] php-fpm.conf is no longer owned by root. [ticket/11337] Run functional tests on travis using nginx and php-fpm. [ticket/11338] Travis CI: Install PHP extension for redis key-value store. [ticket/10050] adding .topicrow to template condition [ticket/9737] Fix a few minor things in migrations ... Conflicts: phpBB/config/services.yml phpBB/config/tables.yml
Diffstat (limited to 'phpBB/includes/db')
-rw-r--r--phpBB/includes/db/db_tools.php31
-rw-r--r--phpBB/includes/db/migration/exception.php55
-rw-r--r--phpBB/includes/db/migration/migration.php190
-rw-r--r--phpBB/includes/db/migration/tool/config.php150
-rw-r--r--phpBB/includes/db/migration/tool/interface.php33
-rw-r--r--phpBB/includes/db/migration/tool/module.php513
-rw-r--r--phpBB/includes/db/migration/tool/permission.php622
-rw-r--r--phpBB/includes/db/migrator.php762
8 files changed, 2355 insertions, 1 deletions
diff --git a/phpBB/includes/db/db_tools.php b/phpBB/includes/db/db_tools.php
index 2bb016cebd..983cdc18ea 100644
--- a/phpBB/includes/db/db_tools.php
+++ b/phpBB/includes/db/db_tools.php
@@ -303,7 +303,7 @@ class phpbb_db_tools
* @param phpbb_db_driver $db Database connection
* @param bool $return_statements True if only statements should be returned and no SQL being executed
*/
- function phpbb_db_tools(&$db, $return_statements = false)
+ function phpbb_db_tools(phpbb_db_driver $db, $return_statements = false)
{
$this->db = $db;
$this->return_statements = $return_statements;
@@ -346,6 +346,17 @@ class phpbb_db_tools
}
/**
+ * Setter for {@link $return_statements return_statements}.
+ *
+ * @param bool $return_statements True if SQL should not be executed but returned as strings
+ * @return null
+ */
+ public function set_return_statements($return_statements)
+ {
+ $this->return_statements = $return_statements;
+ }
+
+ /**
* Gets a list of tables in the database.
*
* @return array Array of table names (all lower case)
@@ -674,6 +685,8 @@ class phpbb_db_tools
* Handle passed database update array.
* Expected structure...
* Key being one of the following
+ * drop_tables: Drop tables
+ * add_tables: Add tables
* change_columns: Column changes (only type, not name)
* add_columns: Add columns to a table
* drop_keys: Dropping keys
@@ -1817,6 +1830,22 @@ class phpbb_db_tools
case 'mssql':
case 'mssqlnative':
+ // remove default cosntraints first
+ // http://msdn.microsoft.com/en-us/library/aa175912%28v=sql.80%29.aspx
+ $statements[] = "DECLARE @drop_default_name VARCHAR(100), @cmd VARCHAR(1000)
+ SET @drop_default_name =
+ (SELECT so.name FROM sysobjects so
+ JOIN sysconstraints sc ON so.id = sc.constid
+ WHERE object_name(so.parent_obj) = '{$table_name}'
+ AND so.xtype = 'D'
+ AND sc.colid = (SELECT colid FROM syscolumns
+ WHERE id = object_id('{$table_name}')
+ AND name = '{$column_name}'))
+ IF @drop_default_name <> ''
+ BEGIN
+ SET @cmd = 'ALTER TABLE [{$table_name}] DROP CONSTRAINT [' + @drop_default_name + ']'
+ EXEC(@cmd)
+ END";
$statements[] = 'ALTER TABLE [' . $table_name . '] DROP COLUMN [' . $column_name . ']';
break;
diff --git a/phpBB/includes/db/migration/exception.php b/phpBB/includes/db/migration/exception.php
new file mode 100644
index 0000000000..ffdcd97780
--- /dev/null
+++ b/phpBB/includes/db/migration/exception.php
@@ -0,0 +1,55 @@
+<?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);
+ }
+}
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..d9cc20053e
--- /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_EXISTS', $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..afe1f21ec5
--- /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_PARENT_NOT_EXIST', $parent);
+ }
+
+ $parent = $data['parent_id'] = $module_id;
+ }
+ else if (!$this->exists($class, false, $parent))
+ {
+ throw new phpbb_db_migration_exception('MODULE_PARENT_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('CANNOT_REMOVE_MODULE', $module_id);
+ }
+ }
+
+ $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..001d090f5a
--- /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_EXISTS', $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)
+ {
+ throw new phpbb_db_migration_exception('ROLE_ALREADY_EXISTS', $old_role_name);
+ }
+
+ $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_EXISTS', $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..4456600b0a
--- /dev/null
+++ b/phpBB/includes/db/migrator.php
@@ -0,0 +1,762 @@
+<?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)
+ {
+ if ($this->unfulfillable($name))
+ {
+ throw new phpbb_db_migration_exception('MIGRATION NOT FULFILLABLE', $name);
+ }
+ }
+ }
+
+ 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 Whether the migration cannot be fulfilled
+ */
+ public function unfulfillable($name)
+ {
+ if (isset($this->migration_state[$name]))
+ {
+ return false;
+ }
+
+ if (!class_exists($name))
+ {
+ return true;
+ }
+
+ $migration = $this->get_migration($name);
+ $depends = $migration->depends_on();
+
+ foreach ($depends as $depend)
+ {
+ if ($this->unfulfillable($depend))
+ {
+ return true;
+ }
+ }
+
+ 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))
+ {
+ 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);
+ }
+}