From a649768e17d25bcf55ae539420abe4eb4b7a1ef1 Mon Sep 17 00:00:00 2001 From: Mate Bartus Date: Wed, 28 Oct 2015 15:00:11 +0100 Subject: [ticket/14262] Move convertor to controller PHPBB3-14262 --- phpBB/install/convert/controller/convertor.php | 871 +++++++++++++++++++++++++ 1 file changed, 871 insertions(+) create mode 100644 phpBB/install/convert/controller/convertor.php (limited to 'phpBB/install/convert/controller') diff --git a/phpBB/install/convert/controller/convertor.php b/phpBB/install/convert/controller/convertor.php new file mode 100644 index 0000000000..c9d78bd438 --- /dev/null +++ b/phpBB/install/convert/controller/convertor.php @@ -0,0 +1,871 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\convert\controller; + +use phpbb\exception\http_exception; +use phpbb\install\controller\helper; +use phpbb\install\helper\container_factory; +use phpbb\install\helper\database; +use phpbb\install\helper\install_helper; +use phpbb\install\helper\iohandler\factory; +use phpbb\install\helper\iohandler\iohandler_interface; +use phpbb\install\helper\navigation\navigation_provider; +use phpbb\language\language; +use phpbb\request\request_interface; +use phpbb\template\template; +use Symfony\Component\HttpFoundation\StreamedResponse; + +/** + * Controller for forum convertors + * + * WARNING: This file did not meant to be present in a production environment, so moving + * this file to a location which is accessible after board installation might + * lead to security issues. + */ +class convertor +{ + /** + * @var \phpbb\cache\driver\driver_interface + */ + protected $cache; + + /** + * @var \phpbb\config\db + */ + protected $config; + + /** + * @var \phpbb\config_php_file + */ + protected $config_php_file; + + /** + * @var string + */ + protected $config_table; + + /** + * @var helper + */ + protected $controller_helper; + + /** + * @var database + */ + protected $db_helper; + + /** + * @var \phpbb\db\driver\driver_interface + */ + protected $db; + + /** + * @var install_helper + */ + protected $install_helper; + + /** + * @var iohandler_interface + */ + protected $iohandler; + + /** + * @var language + */ + protected $language; + + /** + * @var navigation_provider + */ + protected $navigation_provider; + + /** + * @var request_interface + */ + protected $request; + + /** + * @var string + */ + protected $session_keys_table; + + /** + * @var string + */ + protected $session_table; + + /** + * @var template + */ + protected $template; + + /** + * @var string + */ + protected $phpbb_root_path; + + /** + * @var string + */ + protected $php_ext; + + /** + * Constructor + * + * @param container_factory $container + * @param database $db_helper + * @param helper $controller_helper + * @param install_helper $install_helper + * @param factory $iohandler + * @param language $language + * @param navigation_provider $nav + * @param request_interface $request + * @param template $template + * @param string $phpbb_root_path + * @param string $php_ext + */ + public function __construct(container_factory $container, database $db_helper, helper $controller_helper, install_helper $install_helper, factory $iohandler, language $language, navigation_provider $nav, request_interface $request, template $template, $phpbb_root_path, $php_ext) + { + $this->controller_helper = $controller_helper; + $this->db_helper = $db_helper; + $this->install_helper = $install_helper; + $this->language = $language; + $this->navigation_provider = $nav; + $this->request = $request; + $this->template = $template; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + + $iohandler->set_environment('ajax'); + $this->iohandler = $iohandler->get(); + + if (!$this->install_helper->is_phpbb_installed() || !defined('IN_INSTALL')) + { + throw new http_exception(403, 'INSTALL_PHPBB_NOT_INSTALLED'); + } + + $this->controller_helper->handle_language_select(); + + $this->cache = $container->get('cache.driver'); + $this->config = $container->get('config'); + $this->config_php_file = new \phpbb\config_php_file($this->phpbb_root_path, $this->php_ext); + $this->db = $container->get('dbal.conn.driver'); + + $this->config_table = $container->get_parameter('tables.config'); + $this->session_keys_table = $container->get_parameter('tables.sessions_keys'); + $this->session_table = $container->get_parameter('tables.sessions'); + } + + /** + * Render the intro page + * + * @param bool|int $start_new Whether or not to force to start a new convertor + * + * @return \Symfony\Component\HttpFoundation\Response + */ + public function intro($start_new) + { + $this->setup_navigation('intro'); + + if ($start_new) + { + if ($this->request->is_ajax()) + { + $response = new StreamedResponse(); + $iohandler = $this->iohandler; + $url = $this->controller_helper->route('phpbb_convert_intro', array('start_new' => 'new')); + $response->setCallback(function() use ($iohandler, $url) { + $iohandler->redirect($url); + }); + $response->headers->set('X-Accel-Buffering', 'no'); + + return $response; + } + + $this->config['convert_progress'] = ''; + $this->config['convert_db_server'] = ''; + $this->config['convert_db_user'] = ''; + $this->db->sql_query('DELETE FROM ' . $this->config_table . " + WHERE config_name = 'convert_progress' + OR config_name = 'convert_db_server' + OR config_name = 'convert_db_user'" + ); + } + + // Let's see if there is a conversion in the works... + $options = array(); + if (!empty($this->config['convert_progress']) && + !empty($this->config['convert_db_server']) && + !empty($this->config['convert_db_user']) && + !empty($this->config['convert_options'])) + { + $options = unserialize($this->config['convert_progress']); + $options = array_merge($options, + unserialize($this->config['convert_db_server']), + unserialize($this->config['convert_db_user']), + unserialize($this->config['convert_options']) + ); + } + + // This information should have already been checked once, but do it again for safety + if (!empty($options) && !empty($options['tag']) && + isset($options['dbms']) && + isset($options['dbhost']) && + isset($options['dbport']) && + isset($options['dbuser']) && + isset($options['dbpasswd']) && + isset($options['dbname']) && + isset($options['table_prefix'])) + { + $this->template->assign_vars(array( + 'TITLE' => $this->language->lang('CONTINUE_CONVERT'), + 'BODY' => $this->language->lang('CONTINUE_CONVERT_BODY'), + 'S_CONTINUE' => true, + 'U_NEW_ACTION' => $this->controller_helper->route('phpbb_convert_intro', array('start_new' => 'new')), + 'U_CONTINUE_ACTION' => $this->controller_helper->route('phpbb_convert_convert', array('converter' => $options['tag'])), + )); + + return $this->controller_helper->render('installer_convert.html', 'CONTINUE_CONVERT', true); + } + + return $this->render_convert_list(); + } + + /** + * Obtain convertor settings + * + * @param string $convertor Name of the convertor + * + * @return \Symfony\Component\HttpFoundation\Response|StreamedResponse + */ + public function settings($convertor) + { + $this->setup_navigation('settings'); + + require_once ($this->phpbb_root_path . 'includes/constants.' . $this->php_ext); + require_once ($this->phpbb_root_path . 'includes/functions_convert.' . $this->php_ext); + + // Include convertor if available + $convertor_file_path = $this->phpbb_root_path . 'install/convertors/convert_' . $convertor . '.' . $this->php_ext; + if (!file_exists($convertor_file_path)) + { + if ($this->request->is_ajax()) + { + $response = new StreamedResponse(); + $ref = $this; + $response->setCallback(function() use ($ref) { + $ref->render_error('CONVERT_NOT_EXIST'); + }); + $response->headers->set('X-Accel-Buffering', 'no'); + + return $response; + } + + $this->render_error('CONVERT_NOT_EXIST'); + return $this->controller_helper->render('installer_convert.html', 'STAGE_SETTINGS', true); + } + + $get_info = true; + $phpbb_root_path = $this->phpbb_root_path; // These globals are required + $phpEx = $this->php_ext; // See above + include_once ($convertor_file_path); + + // The test_file is a file that should be present in the location of the old board. + if (!isset($test_file)) + { + if ($this->request->is_ajax()) + { + $response = new StreamedResponse(); + $ref = $this; + $response->setCallback(function() use ($ref) { + $ref->render_error('DEV_NO_TEST_FILE'); + }); + $response->headers->set('X-Accel-Buffering', 'no'); + + return $response; + } + + $this->render_error('DEV_NO_TEST_FILE'); + return $this->controller_helper->render('installer_convert.html', 'STAGE_SETTINGS', true); + } + + if ($this->request->variable('submit', false)) + { + // It must be an AJAX request at this point + $response = new StreamedResponse(); + $ref = $this; + $response->setCallback(function() use ($ref, $convertor) { + $ref->proccess_settings_form($convertor); + }); + $response->headers->set('X-Accel-Buffering', 'no'); + + return $response; + } + else + { + $this->template->assign_vars(array( + 'U_ACTION' => $this->controller_helper->route('phpbb_convert_settings', array( + 'convertor' => $convertor, + )) + )); + + if ($this->request->is_ajax()) + { + $response = new StreamedResponse(); + $ref = $this; + $response->setCallback(function() use ($ref) { + $ref->render_settings_form(); + }); + $response->headers->set('X-Accel-Buffering', 'no'); + + return $response; + } + + $this->render_settings_form(); + } + + return $this->controller_helper->render('installer_convert.html', 'STAGE_SETTINGS', true); + } + + /** + * Run conversion + */ + public function convert($converter) + { + $this->setup_navigation('convert'); + + if ($this->request->is_ajax()) + { + $route = $this->controller_helper->route('phpbb_convert_convert', array('converter' => $converter)); + $response = new StreamedResponse(); + $ref = $this; + $response->setCallback(function() use ($ref, $route) { + $ref->redirect_to_html($route); + }); + $response->headers->set('X-Accel-Buffering', 'no'); + + return $response; + } + + $convertor = new \phpbb\convert\convertor($this->template, $this->controller_helper); + $convertor->convert_data($converter); + + return $this->controller_helper->render('installer_convert.html', 'STAGE_IN_PROGRESS'); + } + + /** + * Render the final page of the convertor + */ + public function finish() + { + $this->setup_navigation('finish'); + + $this->template->assign_vars(array( + 'TITLE' => $this->language->lang('CONVERT_COMPLETE'), + 'BODY' => $this->language->lang('CONVERT_COMPLETE_EXPLAIN'), + )); + + // If we reached this step (conversion completed) we want to purge the cache and log the user out. + // This is for making sure the session get not screwed due to the 3.0.x users table being completely new. + $this->cache->purge(); + + require_once($this->phpbb_root_path . 'includes/constants.' . $this->php_ext); + require_once($this->phpbb_root_path . 'includes/functions_convert.' . $this->php_ext); + + $sql = 'SELECT config_value + FROM ' . $this->config_table . ' + WHERE config_name = \'search_type\''; + $result = $this->db->sql_query($sql); + + if ($this->db->sql_fetchfield('config_value') != 'fulltext_mysql') + { + $this->template->assign_vars(array( + 'S_ERROR_BOX' => true, + 'ERROR_TITLE' => $this->language->lang('SEARCH_INDEX_UNCONVERTED'), + 'ERROR_MSG' => $this->language->lang('SEARCH_INDEX_UNCONVERTED_EXPLAIN'), + )); + } + + $this->db->sql_freeresult($result); + + switch ($this->db->get_sql_layer()) + { + case 'sqlite': + case 'sqlite3': + $this->db->sql_query('DELETE FROM ' . $this->session_keys_table); + $this->db->sql_query('DELETE FROM ' . $this->session_table); + break; + + default: + $this->db->sql_query('TRUNCATE TABLE ' . $this->session_keys_table); + $this->db->sql_query('TRUNCATE TABLE ' . $this->session_table); + break; + } + + return $this->controller_helper->render('installer_convert.html', 'CONVERT_COMPLETE'); + } + + /** + * Validates settings form + * + * @param string $convertor + */ + public function proccess_settings_form($convertor) + { + global $phpbb_root_path, $phpEx, $get_info; + + $phpbb_root_path = $this->phpbb_root_path; + $phpEx = $this->php_ext; + $get_info = true; + + require_once($this->phpbb_root_path . 'includes/constants.' . $this->php_ext); + require_once($this->phpbb_root_path . 'includes/functions_convert.' . $this->php_ext); + + // Include convertor if available + $convertor_file_path = $this->phpbb_root_path . 'install/convertors/convert_' . $convertor . '.' . $this->php_ext; + include ($convertor_file_path); + + // We expect to have an AJAX request here + $src_dbms = $this->request->variable('src_dbms', $convertor_data['dbms']); + $src_dbhost = $this->request->variable('src_dbhost', $convertor_data['dbhost']); + $src_dbport = $this->request->variable('src_dbport', $convertor_data['dbport']); + $src_dbuser = $this->request->variable('src_dbuser', $convertor_data['dbuser']); + $src_dbpasswd = $this->request->variable('src_dbpasswd', $convertor_data['dbpasswd']); + $src_dbname = $this->request->variable('src_dbname', $convertor_data['dbname']); + $src_table_prefix = $this->request->variable('src_table_prefix', $convertor_data['table_prefix']); + $forum_path = $this->request->variable('forum_path', $convertor_data['forum_path']); + $refresh = $this->request->variable('refresh', 1); + + // Default URL of the old board + // @todo Are we going to use this for attempting to convert URL references in posts, or should we remove it? + // -> We should convert old urls to the new relative urls format + // $src_url = $request->variable('src_url', 'Not in use at the moment'); + + // strip trailing slash from old forum path + $forum_path = (strlen($forum_path) && $forum_path[strlen($forum_path) - 1] == '/') ? substr($forum_path, 0, -1) : $forum_path; + + $error = array(); + if (!file_exists($this->phpbb_root_path . $forum_path . '/' . $test_file)) + { + $error[] = $this->language->lang('COULD_NOT_FIND_PATH', $forum_path); + } + + $connect_test = false; + $available_dbms = $this->db_helper->get_available_dbms(false, true, true); + if (!isset($available_dbms[$src_dbms]) || !$available_dbms[$src_dbms]['AVAILABLE']) + { + $error[] = $this->language->lang('INST_ERR_NO_DB'); + } + else + { + $connect_test = $this->db_helper->check_database_connection($src_dbms, $src_dbhost, $src_dbport, $src_dbuser, $src_dbpasswd, $src_dbname, $src_table_prefix); + } + + extract($this->config_php_file->get_all()); + + // The forum prefix of the old and the new forum can only be the same if two different databases are used. + if ($src_table_prefix === $table_prefix && $src_dbms === $dbms && $src_dbhost === $dbhost && $src_dbport === $dbport && $src_dbname === $dbname) + { + $error[] = $this->language->lang('TABLE_PREFIX_SAME', $src_table_prefix); + } + + if (!$connect_test) + { + $error[] = $this->language->lang('INST_ERR_DB_CONNECT'); + } + + $src_dbms = $this->config_php_file->convert_30_dbms_to_31($src_dbms); + + // Check table prefix + if (empty($error)) + { + // initiate database connection to old db if old and new db differ + global $src_db, $same_db; + $src_db = $same_db = false; + + if ($src_dbms != $dbms || $src_dbhost != $dbhost || $src_dbport != $dbport || $src_dbname != $dbname || $src_dbuser != $dbuser) + { + /** @var \phpbb\db\driver\driver_interface $src_db */ + $src_db = new $src_dbms(); + $src_db->sql_connect($src_dbhost, $src_dbuser, htmlspecialchars_decode($src_dbpasswd), $src_dbname, $src_dbport, false, true); + $same_db = false; + } + else + { + $src_db = $this->db; + $same_db = true; + } + + $src_db->sql_return_on_error(true); + $this->db->sql_return_on_error(true); + + // Try to select one row from the first table to see if the prefix is OK + $result = $src_db->sql_query_limit('SELECT * FROM ' . $src_table_prefix . $tables[0], 1); + + if (!$result) + { + $prefixes = array(); + + $db_tools_factory = new \phpbb\db\tools\factory(); + $db_tools = $db_tools_factory->get($src_db); + $tables_existing = $db_tools->sql_list_tables(); + $tables_existing = array_map('strtolower', $tables_existing); + foreach ($tables_existing as $table_name) + { + compare_table($tables, $table_name, $prefixes); + } + unset($tables_existing); + + foreach ($prefixes as $prefix => $count) + { + if ($count >= sizeof($tables)) + { + $possible_prefix = $prefix; + break; + } + } + + $msg = ''; + if (!empty($convertor_data['table_prefix'])) + { + $msg .= $this->language->lang_array('DEFAULT_PREFIX_IS', array($convertor_data['forum_name'], $convertor_data['table_prefix'])); + } + + if (!empty($possible_prefix)) + { + $msg .= '
'; + $msg .= ($possible_prefix == '*') ? $this->language->lang('BLANK_PREFIX_FOUND') : $this->language->lang_array('PREFIX_FOUND', array($possible_prefix)); + $src_table_prefix = ($possible_prefix == '*') ? '' : $possible_prefix; + } + + $error[] = $msg; + } + + $src_db->sql_freeresult($result); + $src_db->sql_return_on_error(false); + } + + if (empty($error)) + { + // Save convertor Status + $this->config->set('convert_progress', serialize(array( + 'step' => '', + 'table_prefix' => $src_table_prefix, + 'tag' => $convertor, + )), false); + $this->config->set('convert_db_server', serialize(array( + 'dbms' => $src_dbms, + 'dbhost' => $src_dbhost, + 'dbport' => $src_dbport, + 'dbname' => $src_dbname, + )), false); + $this->config->set('convert_db_user', serialize(array( + 'dbuser' => $src_dbuser, + 'dbpasswd' => $src_dbpasswd, + )), false); + + // Save options + $this->config->set('convert_options', serialize(array( + 'forum_path' => $this->phpbb_root_path . $forum_path, + 'refresh' => $refresh + )), false); + + $url = $this->controller_helper->route('phpbb_convert_convert', array('converter' => $convertor)); + $this->iohandler->redirect($url); + $this->iohandler->send_response(); + } + else + { + $this->render_settings_form($error); + } + } + + /** + * Renders settings form + * + * @param array $error Array of errors + */ + public function render_settings_form($error = array()) + { + foreach ($error as $msg) + { + $this->iohandler->add_error_message($msg); + } + + $dbms_options = array(); + foreach ($this->db_helper->get_available_dbms() as $dbms_key => $dbms_array) + { + $dbms_options[] = array( + 'value' => $dbms_key, + 'label' => 'DB_OPTION_' . strtoupper($dbms_key), + ); + } + + $form_title = 'SPECIFY_OPTIONS'; + $form_data = array( + 'src_dbms' => array( + 'label' => 'DBMS', + 'type' => 'select', + 'options' => $dbms_options, + ), + 'src_dbhost' => array( + 'label' => 'DB_HOST', + 'description' => 'DB_HOST_EXPLAIN', + 'type' => 'text', + ), + 'src_dbport' => array( + 'label' => 'DB_PORT', + 'description' => 'DB_PORT_EXPLAIN', + 'type' => 'text', + ), + 'src_dbname' => array( + 'label' => 'DB_NAME', + 'type' => 'text', + ), + 'src_dbuser' => array( + 'label' => 'DB_USERNAME', + 'type' => 'text', + ), + 'src_dbpasswd' => array( + 'label' => 'DB_PASSWORD', + 'type' => 'password', + ), + 'src_table_prefix' => array( + 'label' => 'TABLE_PREFIX', + 'description' => 'TABLE_PREFIX_EXPLAIN', + 'type' => 'text', + ), + 'forum_path' => array( + 'label' => 'FORUM_PATH', + 'description' => 'FORUM_PATH_EXPLAIN', + 'type' => 'text', + ), + 'refresh' => array( + 'label' => 'REFRESH_PAGE', + 'description' => 'REFRESH_PAGE_EXPLAIN', + 'type' => 'radio', + 'options' => array( + array( + 'value' => 0, + 'label' => 'NO', + 'selected' => true, + ), + array( + 'value' => 1, + 'label' => 'YES', + 'selected' => false, + ), + ), + ), + 'submit' => array( + 'label' => 'SUBMIT', + 'type' => 'submit', + ), + ); + + if ($this->request->is_ajax()) + { + $this->iohandler->add_user_form_group($form_title, $form_data); + $this->iohandler->send_response(); + } + else + { + $rendered_form = $this->iohandler->generate_form_render_data($form_title, $form_data); + + $this->template->assign_vars(array( + 'TITLE' => $this->language->lang('STAGE_SETTINGS'), + 'CONTENT' => $rendered_form, + )); + } + } + + /** + * Render the list of available convertors + * + * @return \Symfony\Component\HttpFoundation\Response + */ + protected function render_convert_list() + { + $this->template->assign_vars(array( + 'TITLE' => $this->language->lang('CONVERT_INTRO'), + 'BODY' => $this->language->lang('CONVERT_INTRO_BODY'), + 'S_LIST' => true, + )); + + $convertors = $sort = array(); + $get_info = true; // Global flag + + $handle = @opendir($this->phpbb_root_path . 'install/convertors/'); + + if (!$handle) + { + die('Unable to access the convertors directory'); + } + + while ($entry = readdir($handle)) + { + if (preg_match('/^convert_([a-z0-9_]+).' . $this->php_ext . '$/i', $entry, $m)) + { + $phpbb_root_path = $this->phpbb_root_path; // These globals are required + $phpEx = $this->php_ext; // See above + include_once($this->phpbb_root_path . 'install/convertors/' . $entry); + if (isset($convertor_data)) + { + $sort[strtolower($convertor_data['forum_name'])] = sizeof($convertors); + + $convertors[] = array( + 'tag' => $m[1], + 'forum_name' => $convertor_data['forum_name'], + 'version' => $convertor_data['version'], + 'dbms' => $convertor_data['dbms'], + 'dbhost' => $convertor_data['dbhost'], + 'dbport' => $convertor_data['dbport'], + 'dbuser' => $convertor_data['dbuser'], + 'dbpasswd' => $convertor_data['dbpasswd'], + 'dbname' => $convertor_data['dbname'], + 'table_prefix' => $convertor_data['table_prefix'], + 'author' => $convertor_data['author'] + ); + } + unset($convertor_data); + } + } + closedir($handle); + + @ksort($sort); + + foreach ($sort as $void => $index) + { + $this->template->assign_block_vars('convertors', array( + 'AUTHOR' => $convertors[$index]['author'], + 'SOFTWARE' => $convertors[$index]['forum_name'], + 'VERSION' => $convertors[$index]['version'], + + 'U_CONVERT' => $this->controller_helper->route('phpbb_convert_settings', array('convertor' => $convertors[$index]['tag'])), + )); + } + + return $this->controller_helper->render('installer_convert.html', 'SUB_INTRO', true); + } + + /** + * Renders an error form + * + * @param string $msg + * @param string|bool $desc + */ + public function render_error($msg, $desc = false) + { + if ($this->request->is_ajax()) + { + $this->iohandler->add_error_message($msg, $desc); + $this->iohandler->send_response(); + } + else + { + $this->template->assign_vars(array( + 'S_ERROR_BOX' => true, + 'ERROR_TITLE' => $this->language->lang($msg), + )); + + if ($desc) + { + $this->template->assign_var('ERROR_MSG', $this->language->lang($desc)); + } + } + } + + /** + * Redirects an AJAX request to a non-JS version + * + * @param string $url URL to redirect to + */ + public function redirect_to_html($url) + { + $this->iohandler->redirect($url); + $this->iohandler->send_response(); + } + + private function setup_navigation($stage) + { + $active = true; + + switch ($stage) + { + case 'finish': + $this->navigation_provider->set_nav_property( + array('convert', 0, 'finish'), + array( + 'selected' => $active, + 'completed' => false, + ) + ); + + $active = false; + + $this->navigation_provider->set_nav_property( + array('convert', 0, 'convert'), + array('completed' => true) + ); + // no break; + + case 'convert': + $this->navigation_provider->set_nav_property( + array('convert', 0, 'convert'), + array( + 'selected' => $active, + 'completed' => false, + ) + ); + + $active = false; + + $this->navigation_provider->set_nav_property( + array('convert', 0, 'settings'), + array('completed' => true) + ); + // no break; + + case 'settings': + $this->navigation_provider->set_nav_property( + array('convert', 0, 'settings'), + array( + 'selected' => $active, + 'completed' => false, + ) + ); + + $active = false; + + $this->navigation_provider->set_nav_property( + array('convert', 0, 'intro'), + array( + 'selected' => $active, + 'completed' => false, + ) + ); + // no break; + + case 'intro': + $this->navigation_provider->set_nav_property( + array('convert', 0, 'intro'), + array( + 'selected' => $active, + 'completed' => false, + ) + ); + break; + } + } +} -- cgit v1.2.1