function convert_data($converter)
{
global $user, $phpbb_root_path, $phpEx, $db, $lang, $config, $cache, $auth;
global $convert, $convert_row, $message_parser, $skip_rows, $language;
global $request, $phpbb_dispatcher;
$phpbb_config_php_file = new \phpbb\config_php_file($phpbb_root_path, $phpEx);
extract($phpbb_config_php_file->get_all());
require_once $phpbb_root_path . 'includes/constants.' . $phpEx;
require_once $phpbb_root_path . 'includes/functions_convert.' . $phpEx;
$dbms = $phpbb_config_php_file->convert_30_dbms_to_31($dbms);
/** @var \phpbb\db\driver\driver_interface $db */
$db = new $dbms();
$db->sql_connect($dbhost, $dbuser, $dbpasswd, $dbname, $dbport, false, true);
unset($dbpasswd);
// We need to fill the config to let internal functions correctly work
$config = new \phpbb\config\db($db, new \phpbb\cache\driver\dummy(), CONFIG_TABLE);
// Override a couple of config variables for the duration
$config['max_quote_depth'] = 0;
// @todo Need to confirm that max post length in source is <= max post length in destination or there may be interesting formatting issues
$config['max_post_chars'] = $config['min_post_chars'] = 0;
// Set up a user as well. We _should_ have enough of a database here at this point to do this
// and it helps for any core code we call
$user->session_begin();
$user->page = $user->extract_current_page($phpbb_root_path);
$convert->options = array();
if (isset($config['convert_progress'])) {
$convert->options = unserialize($config['convert_progress']);
$convert->options = array_merge($convert->options, unserialize($config['convert_db_server']), unserialize($config['convert_db_user']), unserialize($config['convert_options']));
}
// This information should have already been checked once, but do it again for safety
if (empty($convert->options) || empty($convert->options['tag']) || !isset($convert->options['dbms']) || !isset($convert->options['dbhost']) || !isset($convert->options['dbport']) || !isset($convert->options['dbuser']) || !isset($convert->options['dbpasswd']) || !isset($convert->options['dbname']) || !isset($convert->options['table_prefix'])) {
$this->error($user->lang['NO_CONVERT_SPECIFIED'], __LINE__, __FILE__);
}
$this->template->assign_var('S_CONV_IN_PROGRESS', true);
// Make some short variables accessible, for easier referencing
$convert->convertor_tag = basename($convert->options['tag']);
$convert->src_dbms = $convert->options['dbms'];
$convert->src_dbhost = $convert->options['dbhost'];
$convert->src_dbport = $convert->options['dbport'];
$convert->src_dbuser = $convert->options['dbuser'];
$convert->src_dbpasswd = $convert->options['dbpasswd'];
$convert->src_dbname = $convert->options['dbname'];
$convert->src_table_prefix = $convert->options['table_prefix'];
// initiate database connection to old db if old and new db differ
global $src_db, $same_db;
$src_db = $same_db = null;
if ($convert->src_dbms != $dbms || $convert->src_dbhost != $dbhost || $convert->src_dbport != $dbport || $convert->src_dbname != $dbname || $convert->src_dbuser != $dbuser) {
$dbms = $convert->src_dbms;
/** @var \phpbb\db\driver\driver $src_db */
$src_db = new $dbms();
$src_db->sql_connect($convert->src_dbhost, $convert->src_dbuser, htmlspecialchars_decode($convert->src_dbpasswd), $convert->src_dbname, $convert->src_dbport, false, true);
$same_db = false;
} else {
$src_db = $db;
$same_db = true;
}
$convert->mysql_convert = false;
switch ($src_db->sql_layer) {
case 'sqlite3':
$convert->src_truncate_statement = 'DELETE FROM ';
break;
// Thanks MySQL, for silently converting...
// Thanks MySQL, for silently converting...
case 'mysql':
case 'mysql4':
if (version_compare($src_db->sql_server_info(true, false), '4.1.3', '>=')) {
$convert->mysql_convert = true;
}
$convert->src_truncate_statement = 'TRUNCATE TABLE ';
break;
case 'mysqli':
$convert->mysql_convert = true;
$convert->src_truncate_statement = 'TRUNCATE TABLE ';
break;
default:
$convert->src_truncate_statement = 'TRUNCATE TABLE ';
break;
}
if ($convert->mysql_convert && !$same_db) {
$src_db->sql_query("SET NAMES 'binary'");
}
switch ($db->get_sql_layer()) {
case 'sqlite3':
$convert->truncate_statement = 'DELETE FROM ';
break;
default:
$convert->truncate_statement = 'TRUNCATE TABLE ';
break;
}
$get_info = false;
// check security implications of direct inclusion
if (!file_exists('./convertors/convert_' . $convert->convertor_tag . '.' . $phpEx)) {
$this->error($user->lang['CONVERT_NOT_EXIST'], __LINE__, __FILE__);
}
if (file_exists('./convertors/functions_' . $convert->convertor_tag . '.' . $phpEx)) {
include_once './convertors/functions_' . $convert->convertor_tag . '.' . $phpEx;
}
$get_info = true;
include './convertors/convert_' . $convert->convertor_tag . '.' . $phpEx;
// Map some variables...
$convert->convertor_data = $convertor_data;
$convert->tables = $tables;
$convert->config_schema = $config_schema;
// Now include the real data
$get_info = false;
include './convertors/convert_' . $convert->convertor_tag . '.' . $phpEx;
$convert->convertor_data = $convertor_data;
$convert->tables = $tables;
$convert->config_schema = $config_schema;
$convert->convertor = $convertor;
// The test_file is a file that should be present in the location of the old board.
if (!file_exists($convert->options['forum_path'] . '/' . $test_file)) {
$this->error(sprintf($user->lang['COULD_NOT_FIND_PATH'], $convert->options['forum_path']), __LINE__, __FILE__);
}
$search_type = $config['search_type'];
// For conversions we are a bit less strict and set to a search backend we know exist...
if (!class_exists($search_type)) {
$search_type = '\\phpbb\\search\\fulltext_native';
$config->set('search_type', $search_type);
}
if (!class_exists($search_type)) {
trigger_error('NO_SUCH_SEARCH_MODULE');
}
$error = false;
$convert->fulltext_search = new $search_type($error, $phpbb_root_path, $phpEx, $auth, $config, $db, $user, $phpbb_dispatcher);
if ($error) {
trigger_error($error);
}
include_once $phpbb_root_path . 'includes/message_parser.' . $phpEx;
$message_parser = new \parse_message();
$jump = $request->variable('jump', 0);
$final_jump = $request->variable('final_jump', 0);
$sync_batch = $request->variable('sync_batch', -1);
$last_statement = $request->variable('last', 0);
// We are running sync...
if ($sync_batch >= 0) {
$this->sync_forums($converter, $sync_batch);
return;
}
if ($jump) {
$this->jump($converter, $jump, $last_statement);
return;
}
if ($final_jump) {
$this->final_jump($final_jump);
return;
}
$current_table = $request->variable('current_table', 0);
$old_current_table = min(-1, $current_table - 1);
$skip_rows = $request->variable('skip_rows', 0);
if (!$current_table && !$skip_rows) {
if (!$request->variable('confirm', false)) {
// If avatars / ranks / smilies folders are specified make sure they are writable
$bad_folders = array();
$local_paths = array('avatar_path' => path($config['avatar_path']), 'avatar_gallery_path' => path($config['avatar_gallery_path']), 'icons_path' => path($config['icons_path']), 'ranks_path' => path($config['ranks_path']), 'smilies_path' => path($config['smilies_path']));
foreach ($local_paths as $folder => $local_path) {
if (isset($convert->convertor[$folder])) {
if (empty($convert->convertor['test_file'])) {
// test_file is mandantory at the moment so this should never be reached, but just in case...
$this->error($user->lang['DEV_NO_TEST_FILE'], __LINE__, __FILE__);
}
if (!$local_path || !$this->filesystem->is_writable($phpbb_root_path . $local_path)) {
if (!$local_path) {
$bad_folders[] = sprintf($user->lang['CONFIG_PHPBB_EMPTY'], $folder);
} else {
$bad_folders[] = $local_path;
}
}
}
}
if (sizeof($bad_folders)) {
$msg = sizeof($bad_folders) == 1 ? $user->lang['MAKE_FOLDER_WRITABLE'] : $user->lang['MAKE_FOLDERS_WRITABLE'];
sort($bad_folders);
$this->error(sprintf($msg, implode('<br />', $bad_folders)), __LINE__, __FILE__, true);
$this->template->assign_vars(array('L_SUBMIT' => $user->lang['INSTALL_TEST'], 'U_ACTION' => $this->controller_helper->route('phpbb_convert_convert', array('converter' => $converter))));
return;
}
// Grab all the tables used in convertor
$missing_tables = $tables_list = $aliases = array();
foreach ($convert->convertor['schema'] as $schema) {
// Skip those not used (because of addons/plugins not detected)
if (!$schema['target']) {
continue;
}
foreach ($schema as $key => $val) {
// we're dealing with an array like:
// array('forum_status', 'forums.forum_status', 'is_item_locked')
if (is_int($key) && !empty($val[1])) {
$temp_data = $val[1];
if (!is_array($temp_data)) {
$temp_data = array($temp_data);
}
foreach ($temp_data as $value) {
if (preg_match('/([a-z0-9_]+)\\.([a-z0-9_]+)\\)* ?A?S? ?([a-z0-9_]*?)\\.?([a-z0-9_]*)$/i', $value, $m)) {
$table = $convert->src_table_prefix . $m[1];
$tables_list[$table] = $table;
if (!empty($m[3])) {
$aliases[] = $convert->src_table_prefix . $m[3];
}
}
}
} else {
if ($key == 'left_join') {
// Convert the value if it wasn't an array already.
if (!is_array($val)) {
$val = array($val);
}
for ($j = 0, $size = sizeof($val); $j < $size; ++$j) {
if (preg_match('/LEFT JOIN ([a-z0-9_]+) AS ([a-z0-9_]+)/i', $val[$j], $m)) {
$table = $convert->src_table_prefix . $m[1];
$tables_list[$table] = $table;
if (!empty($m[2])) {
$aliases[] = $convert->src_table_prefix . $m[2];
}
}
}
}
}
}
}
// Remove aliased tables from $tables_list
foreach ($aliases as $alias) {
unset($tables_list[$alias]);
}
// Check if the tables that we need exist
$src_db->sql_return_on_error(true);
foreach ($tables_list as $table => $null) {
$sql = 'SELECT 1 FROM ' . $table;
$_result = $src_db->sql_query_limit($sql, 1);
if (!$_result) {
$missing_tables[] = $table;
}
$src_db->sql_freeresult($_result);
}
$src_db->sql_return_on_error(false);
// Throw an error if some tables are missing
// We used to do some guessing here, but since we have a suggestion of possible values earlier, I don't see it adding anything here to do it again
if (sizeof($missing_tables) == sizeof($tables_list)) {
$this->error($user->lang['NO_TABLES_FOUND'] . ' ' . $user->lang['CHECK_TABLE_PREFIX'], __LINE__, __FILE__);
} else {
if (sizeof($missing_tables)) {
$this->error(sprintf($user->lang['TABLES_MISSING'], implode($user->lang['COMMA_SEPARATOR'], $missing_tables)) . '<br /><br />' . $user->lang['CHECK_TABLE_PREFIX'], __LINE__, __FILE__);
}
}
$url = $this->save_convert_progress($converter, 'confirm=1');
$msg = $user->lang['PRE_CONVERT_COMPLETE'];
if ($convert->convertor_data['author_notes']) {
$msg .= '</p><p>' . sprintf($user->lang['AUTHOR_NOTES'], $convert->convertor_data['author_notes']);
}
$this->template->assign_vars(array('L_SUBMIT' => $user->lang['CONTINUE_CONVERT'], 'BODY' => $msg, 'U_ACTION' => $url));
return;
}
// if (!$request->variable('confirm', false)))
$this->template->assign_block_vars('checks', array('S_LEGEND' => true, 'LEGEND' => $user->lang['STARTING_CONVERT']));
// Convert the config table and load the settings of the old board
if (!empty($convert->config_schema)) {
restore_config($convert->config_schema);
// Override a couple of config variables for the duration
$config['max_quote_depth'] = 0;
// @todo Need to confirm that max post length in source is <= max post length in destination or there may be interesting formatting issues
$config['max_post_chars'] = $config['min_post_chars'] = 0;
}
$this->template->assign_block_vars('checks', array('TITLE' => $user->lang['CONFIG_CONVERT'], 'RESULT' => $user->lang['DONE']));
// Now process queries and execute functions that have to be executed prior to the conversion
if (!empty($convert->convertor['execute_first'])) {
// @codingStandardsIgnoreStart
eval($convert->convertor['execute_first']);
// @codingStandardsIgnoreEnd
}
if (!empty($convert->convertor['query_first'])) {
if (!is_array($convert->convertor['query_first'])) {
$convert->convertor['query_first'] = array('target', array($convert->convertor['query_first']));
} else {
if (!is_array($convert->convertor['query_first'][0])) {
$convert->convertor['query_first'] = array(array($convert->convertor['query_first'][0], $convert->convertor['query_first'][1]));
}
}
foreach ($convert->convertor['query_first'] as $query_first) {
if ($query_first[0] == 'src') {
if ($convert->mysql_convert && $same_db) {
$src_db->sql_query("SET NAMES 'binary'");
}
$src_db->sql_query($query_first[1]);
if ($convert->mysql_convert && $same_db) {
$src_db->sql_query("SET NAMES 'utf8'");
}
} else {
$db->sql_query($query_first[1]);
}
}
}
$this->template->assign_block_vars('checks', array('TITLE' => $user->lang['PREPROCESS_STEP'], 'RESULT' => $user->lang['DONE']));
}
// if (!$current_table && !$skip_rows)
$this->template->assign_block_vars('checks', array('S_LEGEND' => true, 'LEGEND' => $user->lang['FILLING_TABLES']));
// This loop takes one target table and processes it
while ($current_table < sizeof($convert->convertor['schema'])) {
$schema = $convert->convertor['schema'][$current_table];
// The target table isn't set, this can be because a module (for example the attachement mod) is taking care of this.
if (empty($schema['target'])) {
$current_table++;
continue;
}
$this->template->assign_block_vars('checks', array('TITLE' => sprintf($user->lang['FILLING_TABLE'], $schema['target'])));
// This is only the case when we first start working on the tables.
if (!$skip_rows) {
// process execute_first and query_first for this table...
if (!empty($schema['execute_first'])) {
// @codingStandardsIgnoreStart
eval($schema['execute_first']);
// @codingStandardsIgnoreEnd
}
if (!empty($schema['query_first'])) {
if (!is_array($schema['query_first'])) {
$schema['query_first'] = array('target', array($schema['query_first']));
} else {
if (!is_array($schema['query_first'][0])) {
$schema['query_first'] = array(array($schema['query_first'][0], $schema['query_first'][1]));
}
}
foreach ($schema['query_first'] as $query_first) {
if ($query_first[0] == 'src') {
if ($convert->mysql_convert && $same_db) {
$src_db->sql_query("SET NAMES 'binary'");
}
$src_db->sql_query($query_first[1]);
if ($convert->mysql_convert && $same_db) {
$src_db->sql_query("SET NAMES 'utf8'");
}
} else {
$db->sql_query($query_first[1]);
}
}
}
if (!empty($schema['autoincrement'])) {
switch ($db->get_sql_layer()) {
case 'postgres':
$db->sql_query("SELECT SETVAL('" . $schema['target'] . "_seq',(select case when max(" . $schema['autoincrement'] . ")>0 then max(" . $schema['autoincrement'] . ")+1 else 1 end from " . $schema['target'] . '));');
break;
case 'oracle':
$result = $db->sql_query('SELECT MAX(' . $schema['autoincrement'] . ') as max_id FROM ' . $schema['target']);
$row = $db->sql_fetchrow($result);
$db->sql_freeresult($result);
$largest_id = (int) $row['max_id'];
if ($largest_id) {
$db->sql_query('DROP SEQUENCE ' . $schema['target'] . '_seq');
$db->sql_query('CREATE SEQUENCE ' . $schema['target'] . '_seq START WITH ' . ($largest_id + 1));
}
break;
}
}
}
// Process execute_always for this table
// This is for code which needs to be executed on every pass of this table if
// it gets split because of time restrictions
if (!empty($schema['execute_always'])) {
// @codingStandardsIgnoreStart
eval($schema['execute_always']);
// @codingStandardsIgnoreEnd
}
//
// Set up some variables
//
// $waiting_rows holds rows for multirows insertion (MySQL only)
// $src_tables holds unique tables with aliases to select from
// $src_fields will quickly refer source fields (or aliases) corresponding to the current index
// $select_fields holds the names of the fields to retrieve
//
$sql_data = array('source_fields' => array(), 'target_fields' => array(), 'source_tables' => array(), 'select_fields' => array());
// This statement is building the keys for later insertion.
$insert_query = $this->build_insert_query($schema, $sql_data, $current_table);
// If no source table is affected, we skip the table
if (empty($sql_data['source_tables'])) {
$skip_rows = 0;
$current_table++;
continue;
}
$distinct = !empty($schema['distinct']) ? 'DISTINCT ' : '';
$sql = 'SELECT ' . $distinct . implode(', ', $sql_data['select_fields']) . " \nFROM " . implode(', ', $sql_data['source_tables']);
// Where
$sql .= !empty($schema['where']) ? "\nWHERE (" . $schema['where'] . ')' : '';
// Group By
if (!empty($schema['group_by'])) {
$schema['group_by'] = array($schema['group_by']);
foreach ($sql_data['select_fields'] as $select) {
$alias = strpos(strtolower($select), ' as ');
$select = $alias ? substr($select, 0, $alias) : $select;
if (!in_array($select, $schema['group_by'])) {
$schema['group_by'][] = $select;
}
}
}
$sql .= !empty($schema['group_by']) ? "\nGROUP BY " . implode(', ', $schema['group_by']) : '';
// Having
$sql .= !empty($schema['having']) ? "\nHAVING " . $schema['having'] : '';
// Order By
if (empty($schema['order_by']) && !empty($schema['primary'])) {
$schema['order_by'] = $schema['primary'];
}
$sql .= !empty($schema['order_by']) ? "\nORDER BY " . $schema['order_by'] : '';
// Counting basically holds the amount of rows processed.
$counting = -1;
$batch_time = 0;
while ($counting === -1 || $counting >= $convert->batch_size && still_on_time()) {
$old_current_table = $current_table;
$rows = '';
$waiting_rows = array();
if (!empty($batch_time)) {
$mtime = explode(' ', microtime());
$mtime = $mtime[0] + $mtime[1];
$rows = ceil($counting / ($mtime - $batch_time)) . " rows/s ({$counting} rows) | ";
}
$this->template->assign_block_vars('checks', array('TITLE' => "skip_rows = {$skip_rows}", 'RESULT' => $rows . (defined('DEBUG') && function_exists('memory_get_usage') ? ceil(memory_get_usage() / 1024) . ' ' . $user->lang['KIB'] : '')));
$mtime = explode(' ', microtime());
$batch_time = $mtime[0] + $mtime[1];
if ($convert->mysql_convert && $same_db) {
$src_db->sql_query("SET NAMES 'binary'");
}
// Take skip rows into account and only fetch batch_size amount of rows
$___result = $src_db->sql_query_limit($sql, $convert->batch_size, $skip_rows);
if ($convert->mysql_convert && $same_db) {
$src_db->sql_query("SET NAMES 'utf8'");
}
// This loop processes each row
$counting = 0;
$convert->row = $convert_row = array();
if (!empty($schema['autoincrement'])) {
switch ($db->get_sql_layer()) {
case 'mssql_odbc':
case 'mssqlnative':
$db->sql_query('SET IDENTITY_INSERT ' . $schema['target'] . ' ON');
break;
}
}
// Now handle the rows until time is over or no more rows to process...
while ($counting === 0 || still_on_time()) {
$convert_row = $src_db->sql_fetchrow($___result);
if (!$convert_row) {
// move to the next batch or table
break;
}
// With this we are able to always save the last state
$convert->row = $convert_row;
// Increment the counting variable, it stores the number of rows we have processed
$counting++;
$insert_values = array();
$sql_flag = $this->process_row($schema, $sql_data, $insert_values);
if ($sql_flag === true) {
switch ($db->get_sql_layer()) {
// If MySQL, we'll wait to have num_wait_rows rows to submit at once
case 'mysql':
case 'mysql4':
case 'mysqli':
$waiting_rows[] = '(' . implode(', ', $insert_values) . ')';
if (sizeof($waiting_rows) >= $convert->num_wait_rows) {
$errored = false;
$db->sql_return_on_error(true);
if (!$db->sql_query($insert_query . implode(', ', $waiting_rows))) {
$errored = true;
}
$db->sql_return_on_error(false);
if ($errored) {
$db->sql_return_on_error(true);
// Because it errored out we will try to insert the rows one by one... most of the time this
// is caused by duplicate entries - but we also do not want to miss one...
foreach ($waiting_rows as $waiting_sql) {
if (!$db->sql_query($insert_query . $waiting_sql)) {
$this->db_error($user->lang['DB_ERR_INSERT'], htmlspecialchars($insert_query . $waiting_sql) . '<br /><br />' . htmlspecialchars(print_r($db->_sql_error(), true)), __LINE__, __FILE__, true);
}
}
$db->sql_return_on_error(false);
}
$waiting_rows = array();
}
break;
default:
$insert_sql = $insert_query . '(' . implode(', ', $insert_values) . ')';
$db->sql_return_on_error(true);
if (!$db->sql_query($insert_sql)) {
$this->db_error($user->lang['DB_ERR_INSERT'], htmlspecialchars($insert_sql) . '<br /><br />' . htmlspecialchars(print_r($db->_sql_error(), true)), __LINE__, __FILE__, true);
}
$db->sql_return_on_error(false);
$waiting_rows = array();
break;
}
}
$skip_rows++;
}
$src_db->sql_freeresult($___result);
// We might still have some rows waiting
if (sizeof($waiting_rows)) {
$errored = false;
$db->sql_return_on_error(true);
if (!$db->sql_query($insert_query . implode(', ', $waiting_rows))) {
$errored = true;
}
$db->sql_return_on_error(false);
if ($errored) {
$db->sql_return_on_error(true);
// Because it errored out we will try to insert the rows one by one... most of the time this
// is caused by duplicate entries - but we also do not want to miss one...
foreach ($waiting_rows as $waiting_sql) {
$db->sql_query($insert_query . $waiting_sql);
$this->db_error($user->lang['DB_ERR_INSERT'], htmlspecialchars($insert_query . $waiting_sql) . '<br /><br />' . htmlspecialchars(print_r($db->_sql_error(), true)), __LINE__, __FILE__, true);
}
$db->sql_return_on_error(false);
}
$waiting_rows = array();
}
if (!empty($schema['autoincrement'])) {
switch ($db->get_sql_layer()) {
case 'mssql_odbc':
case 'mssqlnative':
$db->sql_query('SET IDENTITY_INSERT ' . $schema['target'] . ' OFF');
break;
case 'postgres':
$db->sql_query("SELECT SETVAL('" . $schema['target'] . "_seq',(select case when max(" . $schema['autoincrement'] . ")>0 then max(" . $schema['autoincrement'] . ")+1 else 1 end from " . $schema['target'] . '));');
break;
case 'oracle':
$result = $db->sql_query('SELECT MAX(' . $schema['autoincrement'] . ') as max_id FROM ' . $schema['target']);
$row = $db->sql_fetchrow($result);
$db->sql_freeresult($result);
$largest_id = (int) $row['max_id'];
if ($largest_id) {
$db->sql_query('DROP SEQUENCE ' . $schema['target'] . '_seq');
$db->sql_query('CREATE SEQUENCE ' . $schema['target'] . '_seq START WITH ' . ($largest_id + 1));
}
break;
}
}
}
// When we reach this point, either the current table has been processed or we're running out of time.
if (still_on_time() && $counting < $convert->batch_size) {
$skip_rows = 0;
$current_table++;
} else {
/*
if (still_on_time() && $counting < $convert->batch_size)
{
$skip_rows = 0;
$current_table++;
}*/
// Looks like we ran out of time.
$url = $this->save_convert_progress($converter, 'current_table=' . $current_table . '&skip_rows=' . $skip_rows);
$current_table++;
// $percentage = ($skip_rows == 0) ? 0 : floor(100 / ($total_rows / $skip_rows));
$msg = sprintf($user->lang['STEP_PERCENT_COMPLETED'], $current_table, sizeof($convert->convertor['schema']));
$this->template->assign_vars(array('BODY' => $msg, 'L_SUBMIT' => $user->lang['CONTINUE_CONVERT'], 'U_ACTION' => $url));
$this->meta_refresh($url);
return;
}
}
// Process execute_last then we'll be done
$url = $this->save_convert_progress($converter, 'jump=1');
$this->template->assign_vars(array('L_SUBMIT' => $user->lang['FINAL_STEP'], 'U_ACTION' => $url));
$this->meta_refresh($url);
return;
}