Verzeichnisstruktur phpBB-3.2.0


Veröffentlicht
06.01.2017

So funktioniert es


Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück

Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis.
Auf den Verzeichnisnamen klicken, dies zeigt nur das Verzeichnis mit Inhalt an

(Beispiel Datei-Icons)

Auf das Icon klicken um den Quellcode anzuzeigen

convertor.php

Zuletzt modifiziert: 09.10.2024, 12:52 - Dateigröße: 45.67 KiB


0001  <?php
0002  /**
0003   *
0004   * This file is part of the phpBB Forum Software package.
0005   *
0006   * @copyright (c) phpBB Limited <https://www.phpbb.com>
0007   * @license GNU General Public License, version 2 (GPL-2.0)
0008   *
0009   * For full copyright and license information, please see
0010   * the docs/CREDITS.txt file.
0011   *
0012   */
0013   
0014  namespace phpbb\convert;
0015   
0016  use phpbb\install\controller\helper;
0017  use phpbb\template\template;
0018   
0019  /**
0020   * Convertor backend class
0021   *
0022   * WARNING: This file did not meant to be present in a production environment, so moving this file to a location which
0023   *             is accessible after board installation might lead to security issues.
0024   */
0025  class convertor
0026  {
0027      /**
0028       * @var helper
0029       */
0030      protected $controller_helper;
0031   
0032      /**
0033       * @var \phpbb\filesystem\filesystem
0034       */
0035      protected $filesystem;
0036   
0037      /**
0038       * @var \phpbb\template\template
0039       */
0040      protected $template;
0041   
0042      /**
0043       * Constructor
0044       *
0045       * @param template    $template
0046       * @param helper    $controller_helper
0047       */
0048      public function __construct(template $template, helper $controller_helper)
0049      {
0050          global $convert, $phpbb_filesystem;
0051   
0052          $this->template = $template;
0053          $this->filesystem = $phpbb_filesystem;
0054          $this->controller_helper = $controller_helper;
0055   
0056          $convert = new convert($this);
0057      }
0058   
0059      /**
0060       * The function which does the actual work (or dispatches it to the relevant places)
0061       */
0062      function convert_data($converter)
0063      {
0064          global $user, $phpbb_root_path, $phpEx, $db, $lang, $config, $cache, $auth;
0065          global $convert, $convert_row, $message_parser, $skip_rows, $language;
0066          global $request, $phpbb_dispatcher;
0067   
0068          $phpbb_config_php_file = new \phpbb\config_php_file($phpbb_root_path, $phpEx);
0069          extract($phpbb_config_php_file->get_all());
0070   
0071          require_once($phpbb_root_path . 'includes/constants.' . $phpEx);
0072          require_once($phpbb_root_path . 'includes/functions_convert.' . $phpEx);
0073   
0074          $dbms = $phpbb_config_php_file->convert_30_dbms_to_31($dbms);
0075   
0076          /** @var \phpbb\db\driver\driver_interface $db */
0077          $db = new $dbms();
0078          $db->sql_connect($dbhost, $dbuser, $dbpasswd, $dbname, $dbport, false, true);
0079          unset($dbpasswd);
0080   
0081          // We need to fill the config to let internal functions correctly work
0082          $config = new \phpbb\config\db($db, new \phpbb\cache\driver\dummy, CONFIG_TABLE);
0083   
0084          // Override a couple of config variables for the duration
0085          $config['max_quote_depth'] = 0;
0086   
0087          // @todo Need to confirm that max post length in source is <= max post length in destination or there may be interesting formatting issues
0088          $config['max_post_chars'] = $config['min_post_chars'] = 0;
0089   
0090          // Set up a user as well. We _should_ have enough of a database here at this point to do this
0091          // and it helps for any core code we call
0092          $user->session_begin();
0093          $user->page = $user->extract_current_page($phpbb_root_path);
0094   
0095          $convert->options = array();
0096          if (isset($config['convert_progress']))
0097          {
0098              $convert->options = unserialize($config['convert_progress']);
0099              $convert->options = array_merge($convert->options, unserialize($config['convert_db_server']), unserialize($config['convert_db_user']), unserialize($config['convert_options']));
0100          }
0101   
0102          // This information should have already been checked once, but do it again for safety
0103          if (empty($convert->options) || empty($convert->options['tag']) ||
0104              !isset($convert->options['dbms']) ||
0105              !isset($convert->options['dbhost']) ||
0106              !isset($convert->options['dbport']) ||
0107              !isset($convert->options['dbuser']) ||
0108              !isset($convert->options['dbpasswd']) ||
0109              !isset($convert->options['dbname']) ||
0110              !isset($convert->options['table_prefix']))
0111          {
0112              $this->error($user->lang['NO_CONVERT_SPECIFIED'], __LINE__, __FILE__);
0113          }
0114   
0115          $this->template->assign_var('S_CONV_IN_PROGRESS', true);
0116   
0117          // Make some short variables accessible, for easier referencing
0118          $convert->convertor_tag = basename($convert->options['tag']);
0119          $convert->src_dbms = $convert->options['dbms'];
0120          $convert->src_dbhost = $convert->options['dbhost'];
0121          $convert->src_dbport = $convert->options['dbport'];
0122          $convert->src_dbuser = $convert->options['dbuser'];
0123          $convert->src_dbpasswd = $convert->options['dbpasswd'];
0124          $convert->src_dbname = $convert->options['dbname'];
0125          $convert->src_table_prefix = $convert->options['table_prefix'];
0126   
0127          // initiate database connection to old db if old and new db differ
0128          global $src_db, $same_db;
0129          $src_db = $same_db = null;
0130          if ($convert->src_dbms != $dbms || $convert->src_dbhost != $dbhost || $convert->src_dbport != $dbport || $convert->src_dbname != $dbname || $convert->src_dbuser != $dbuser)
0131          {
0132              $dbms = $convert->src_dbms;
0133              /** @var \phpbb\db\driver\driver $src_db */
0134              $src_db = new $dbms();
0135              $src_db->sql_connect($convert->src_dbhost, $convert->src_dbuser, htmlspecialchars_decode($convert->src_dbpasswd), $convert->src_dbname, $convert->src_dbport, false, true);
0136              $same_db = false;
0137          }
0138          else
0139          {
0140              $src_db = $db;
0141              $same_db = true;
0142          }
0143   
0144          $convert->mysql_convert = false;
0145          switch ($src_db->sql_layer)
0146          {
0147              case 'sqlite3':
0148                  $convert->src_truncate_statement = 'DELETE FROM ';
0149                  break;
0150   
0151              // Thanks MySQL, for silently converting...
0152              case 'mysql':
0153              case 'mysql4':
0154                  if (version_compare($src_db->sql_server_info(true, false), '4.1.3', '>='))
0155                  {
0156                      $convert->mysql_convert = true;
0157                  }
0158                  $convert->src_truncate_statement = 'TRUNCATE TABLE ';
0159                  break;
0160   
0161              case 'mysqli':
0162                  $convert->mysql_convert = true;
0163                  $convert->src_truncate_statement = 'TRUNCATE TABLE ';
0164                  break;
0165   
0166              default:
0167                  $convert->src_truncate_statement = 'TRUNCATE TABLE ';
0168                  break;
0169          }
0170   
0171          if ($convert->mysql_convert && !$same_db)
0172          {
0173              $src_db->sql_query("SET NAMES 'binary'");
0174          }
0175   
0176          switch ($db->get_sql_layer())
0177          {
0178              case 'sqlite3':
0179                  $convert->truncate_statement = 'DELETE FROM ';
0180                  break;
0181   
0182              default:
0183                  $convert->truncate_statement = 'TRUNCATE TABLE ';
0184                  break;
0185          }
0186   
0187          $get_info = false;
0188   
0189          // check security implications of direct inclusion
0190          if (!file_exists('./convertors/convert_' . $convert->convertor_tag . '.' . $phpEx))
0191          {
0192              $this->error($user->lang['CONVERT_NOT_EXIST'], __LINE__, __FILE__);
0193          }
0194   
0195          if (file_exists('./convertors/functions_' . $convert->convertor_tag . '.' . $phpEx))
0196          {
0197              include_once('./convertors/functions_' . $convert->convertor_tag . '.' . $phpEx);
0198          }
0199   
0200          $get_info = true;
0201          include('./convertors/convert_' . $convert->convertor_tag . '.' . $phpEx);
0202   
0203          // Map some variables...
0204          $convert->convertor_data = $convertor_data;
0205          $convert->tables = $tables;
0206          $convert->config_schema = $config_schema;
0207   
0208          // Now include the real data
0209          $get_info = false;
0210          include('./convertors/convert_' . $convert->convertor_tag . '.' . $phpEx);
0211   
0212          $convert->convertor_data = $convertor_data;
0213          $convert->tables = $tables;
0214          $convert->config_schema = $config_schema;
0215          $convert->convertor = $convertor;
0216   
0217          // The test_file is a file that should be present in the location of the old board.
0218          if (!file_exists($convert->options['forum_path'] . '/' . $test_file))
0219          {
0220              $this->error(sprintf($user->lang['COULD_NOT_FIND_PATH'], $convert->options['forum_path']), __LINE__, __FILE__);
0221          }
0222   
0223          $search_type = $config['search_type'];
0224   
0225          // For conversions we are a bit less strict and set to a search backend we know exist...
0226          if (!class_exists($search_type))
0227          {
0228              $search_type = '\phpbb\search\fulltext_native';
0229              $config->set('search_type', $search_type);
0230          }
0231   
0232          if (!class_exists($search_type))
0233          {
0234              trigger_error('NO_SUCH_SEARCH_MODULE');
0235          }
0236   
0237          $error = false;
0238          $convert->fulltext_search = new $search_type($error, $phpbb_root_path, $phpEx, $auth, $config, $db, $user, $phpbb_dispatcher);
0239   
0240          if ($error)
0241          {
0242              trigger_error($error);
0243          }
0244   
0245          include_once($phpbb_root_path . 'includes/message_parser.' . $phpEx);
0246          $message_parser = new \parse_message();
0247   
0248          $jump = $request->variable('jump', 0);
0249          $final_jump = $request->variable('final_jump', 0);
0250          $sync_batch = $request->variable('sync_batch', -1);
0251          $last_statement = $request->variable('last', 0);
0252   
0253          // We are running sync...
0254          if ($sync_batch >= 0)
0255          {
0256              $this->sync_forums($converter, $sync_batch);
0257              return;
0258          }
0259   
0260          if ($jump)
0261          {
0262              $this->jump($converter, $jump, $last_statement);
0263              return;
0264          }
0265   
0266          if ($final_jump)
0267          {
0268              $this->final_jump($final_jump);
0269              return;
0270          }
0271   
0272          $current_table = $request->variable('current_table', 0);
0273          $old_current_table = min(-1, $current_table - 1);
0274          $skip_rows = $request->variable('skip_rows', 0);
0275   
0276          if (!$current_table && !$skip_rows)
0277          {
0278              if (!$request->variable('confirm', false))
0279              {
0280                  // If avatars / ranks / smilies folders are specified make sure they are writable
0281                  $bad_folders = array();
0282   
0283                  $local_paths = array(
0284                      'avatar_path'            => path($config['avatar_path']),
0285                      'avatar_gallery_path'    => path($config['avatar_gallery_path']),
0286                      'icons_path'            => path($config['icons_path']),
0287                      'ranks_path'            => path($config['ranks_path']),
0288                      'smilies_path'            => path($config['smilies_path'])
0289                  );
0290   
0291                  foreach ($local_paths as $folder => $local_path)
0292                  {
0293                      if (isset($convert->convertor[$folder]))
0294                      {
0295                          if (empty($convert->convertor['test_file']))
0296                          {
0297                              // test_file is mandantory at the moment so this should never be reached, but just in case...
0298                              $this->error($user->lang['DEV_NO_TEST_FILE'], __LINE__, __FILE__);
0299                          }
0300   
0301                          if (!$local_path || !$this->filesystem->is_writable($phpbb_root_path . $local_path))
0302                          {
0303                              if (!$local_path)
0304                              {
0305                                  $bad_folders[] = sprintf($user->lang['CONFIG_PHPBB_EMPTY'], $folder);
0306                              }
0307                              else
0308                              {
0309                                  $bad_folders[] = $local_path;
0310                              }
0311                          }
0312                      }
0313                  }
0314   
0315                  if (sizeof($bad_folders))
0316                  {
0317                      $msg = (sizeof($bad_folders) == 1) ? $user->lang['MAKE_FOLDER_WRITABLE'] : $user->lang['MAKE_FOLDERS_WRITABLE'];
0318                      sort($bad_folders);
0319                      $this->error(sprintf($msg, implode('<br />', $bad_folders)), __LINE__, __FILE__, true);
0320   
0321                      $this->template->assign_vars(array(
0322                          'L_SUBMIT'    => $user->lang['INSTALL_TEST'],
0323                          'U_ACTION'    => $this->controller_helper->route('phpbb_convert_convert', array('converter' => $converter)),
0324                      ));
0325                      return;
0326                  }
0327   
0328                  // Grab all the tables used in convertor
0329                  $missing_tables = $tables_list = $aliases = array();
0330   
0331                  foreach ($convert->convertor['schema'] as $schema)
0332                  {
0333                      // Skip those not used (because of addons/plugins not detected)
0334                      if (!$schema['target'])
0335                      {
0336                          continue;
0337                      }
0338   
0339                      foreach ($schema as $key => $val)
0340                      {
0341                          // we're dealing with an array like:
0342                          // array('forum_status',            'forums.forum_status',                'is_item_locked')
0343                          if (is_int($key) && !empty($val[1]))
0344                          {
0345                              $temp_data = $val[1];
0346                              if (!is_array($temp_data))
0347                              {
0348                                  $temp_data = array($temp_data);
0349                              }
0350   
0351                              foreach ($temp_data as $value)
0352                              {
0353                                  if (preg_match('/([a-z0-9_]+)\.([a-z0-9_]+)\)* ?A?S? ?([a-z0-9_]*?)\.?([a-z0-9_]*)$/i', $value, $m))
0354                                  {
0355                                      $table = $convert->src_table_prefix . $m[1];
0356                                      $tables_list[$table] = $table;
0357   
0358                                      if (!empty($m[3]))
0359                                      {
0360                                          $aliases[] = $convert->src_table_prefix . $m[3];
0361                                      }
0362                                  }
0363                              }
0364                          }
0365                          // 'left_join'        => 'topics LEFT JOIN vote_desc ON topics.topic_id = vote_desc.topic_id AND topics.topic_vote = 1'
0366                          else if ($key == 'left_join')
0367                          {
0368                              // Convert the value if it wasn't an array already.
0369                              if (!is_array($val))
0370                              {
0371                                  $val = array($val);
0372                              }
0373   
0374                              for ($j = 0, $size = sizeof($val); $j < $size; ++$j)
0375                              {
0376                                  if (preg_match('/LEFT JOIN ([a-z0-9_]+) AS ([a-z0-9_]+)/i', $val[$j], $m))
0377                                  {
0378                                      $table = $convert->src_table_prefix . $m[1];
0379                                      $tables_list[$table] = $table;
0380   
0381                                      if (!empty($m[2]))
0382                                      {
0383                                          $aliases[] = $convert->src_table_prefix . $m[2];
0384                                      }
0385                                  }
0386                              }
0387                          }
0388                      }
0389                  }
0390   
0391                  // Remove aliased tables from $tables_list
0392                  foreach ($aliases as $alias)
0393                  {
0394                      unset($tables_list[$alias]);
0395                  }
0396   
0397                  // Check if the tables that we need exist
0398                  $src_db->sql_return_on_error(true);
0399                  foreach ($tables_list as $table => $null)
0400                  {
0401                      $sql = 'SELECT 1 FROM ' . $table;
0402                      $_result = $src_db->sql_query_limit($sql, 1);
0403   
0404                      if (!$_result)
0405                      {
0406                          $missing_tables[] = $table;
0407                      }
0408                      $src_db->sql_freeresult($_result);
0409                  }
0410                  $src_db->sql_return_on_error(false);
0411   
0412                  // Throw an error if some tables are missing
0413                  // 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
0414   
0415                  if (sizeof($missing_tables) == sizeof($tables_list))
0416                  {
0417                      $this->error($user->lang['NO_TABLES_FOUND'] . ' ' . $user->lang['CHECK_TABLE_PREFIX'], __LINE__, __FILE__);
0418                  }
0419                  else if (sizeof($missing_tables))
0420                  {
0421                      $this->error(sprintf($user->lang['TABLES_MISSING'], implode($user->lang['COMMA_SEPARATOR'], $missing_tables)) . '<br /><br />' . $user->lang['CHECK_TABLE_PREFIX'], __LINE__, __FILE__);
0422                  }
0423   
0424                  $url = $this->save_convert_progress($converter, 'confirm=1');
0425                  $msg = $user->lang['PRE_CONVERT_COMPLETE'];
0426   
0427                  if ($convert->convertor_data['author_notes'])
0428                  {
0429                      $msg .= '</p><p>' . sprintf($user->lang['AUTHOR_NOTES'], $convert->convertor_data['author_notes']);
0430                  }
0431   
0432                  $this->template->assign_vars(array(
0433                      'L_SUBMIT'        => $user->lang['CONTINUE_CONVERT'],
0434                      'BODY'            => $msg,
0435                      'U_ACTION'        => $url,
0436                  ));
0437   
0438                  return;
0439              } // if (!$request->variable('confirm', false)))
0440   
0441              $this->template->assign_block_vars('checks', array(
0442                  'S_LEGEND'        => true,
0443                  'LEGEND'        => $user->lang['STARTING_CONVERT'],
0444              ));
0445   
0446              // Convert the config table and load the settings of the old board
0447              if (!empty($convert->config_schema))
0448              {
0449                  restore_config($convert->config_schema);
0450   
0451                  // Override a couple of config variables for the duration
0452                  $config['max_quote_depth'] = 0;
0453   
0454                  // @todo Need to confirm that max post length in source is <= max post length in destination or there may be interesting formatting issues
0455                  $config['max_post_chars'] = $config['min_post_chars'] = 0;
0456              }
0457   
0458              $this->template->assign_block_vars('checks', array(
0459                  'TITLE'        => $user->lang['CONFIG_CONVERT'],
0460                  'RESULT'    => $user->lang['DONE'],
0461              ));
0462   
0463              // Now process queries and execute functions that have to be executed prior to the conversion
0464              if (!empty($convert->convertor['execute_first']))
0465              {
0466                  // @codingStandardsIgnoreStart
0467                  eval($convert->convertor['execute_first']);
0468                  // @codingStandardsIgnoreEnd
0469              }
0470   
0471              if (!empty($convert->convertor['query_first']))
0472              {
0473                  if (!is_array($convert->convertor['query_first']))
0474                  {
0475                      $convert->convertor['query_first'] = array('target', array($convert->convertor['query_first']));
0476                  }
0477                  else if (!is_array($convert->convertor['query_first'][0]))
0478                  {
0479                      $convert->convertor['query_first'] = array(array($convert->convertor['query_first'][0], $convert->convertor['query_first'][1]));
0480                  }
0481   
0482                  foreach ($convert->convertor['query_first'] as $query_first)
0483                  {
0484                      if ($query_first[0] == 'src')
0485                      {
0486                          if ($convert->mysql_convert && $same_db)
0487                          {
0488                              $src_db->sql_query("SET NAMES 'binary'");
0489                          }
0490   
0491                          $src_db->sql_query($query_first[1]);
0492   
0493                          if ($convert->mysql_convert && $same_db)
0494                          {
0495                              $src_db->sql_query("SET NAMES 'utf8'");
0496                          }
0497                      }
0498                      else
0499                      {
0500                          $db->sql_query($query_first[1]);
0501                      }
0502                  }
0503              }
0504   
0505              $this->template->assign_block_vars('checks', array(
0506                  'TITLE'        => $user->lang['PREPROCESS_STEP'],
0507                  'RESULT'    => $user->lang['DONE'],
0508              ));
0509          } // if (!$current_table && !$skip_rows)
0510   
0511          $this->template->assign_block_vars('checks', array(
0512              'S_LEGEND'        => true,
0513              'LEGEND'        => $user->lang['FILLING_TABLES'],
0514          ));
0515   
0516          // This loop takes one target table and processes it
0517          while ($current_table < sizeof($convert->convertor['schema']))
0518          {
0519              $schema = $convert->convertor['schema'][$current_table];
0520   
0521              // The target table isn't set, this can be because a module (for example the attachement mod) is taking care of this.
0522              if (empty($schema['target']))
0523              {
0524                  $current_table++;
0525                  continue;
0526              }
0527   
0528              $this->template->assign_block_vars('checks', array(
0529                  'TITLE'    => sprintf($user->lang['FILLING_TABLE'], $schema['target']),
0530              ));
0531   
0532              // This is only the case when we first start working on the tables.
0533              if (!$skip_rows)
0534              {
0535                  // process execute_first and query_first for this table...
0536                  if (!empty($schema['execute_first']))
0537                  {
0538                      // @codingStandardsIgnoreStart
0539                      eval($schema['execute_first']);
0540                      // @codingStandardsIgnoreEnd
0541                  }
0542   
0543                  if (!empty($schema['query_first']))
0544                  {
0545                      if (!is_array($schema['query_first']))
0546                      {
0547                          $schema['query_first'] = array('target', array($schema['query_first']));
0548                      }
0549                      else if (!is_array($schema['query_first'][0]))
0550                      {
0551                          $schema['query_first'] = array(array($schema['query_first'][0], $schema['query_first'][1]));
0552                      }
0553   
0554                      foreach ($schema['query_first'] as $query_first)
0555                      {
0556                          if ($query_first[0] == 'src')
0557                          {
0558                              if ($convert->mysql_convert && $same_db)
0559                              {
0560                                  $src_db->sql_query("SET NAMES 'binary'");
0561                              }
0562                              $src_db->sql_query($query_first[1]);
0563                              if ($convert->mysql_convert && $same_db)
0564                              {
0565                                  $src_db->sql_query("SET NAMES 'utf8'");
0566                              }
0567                          }
0568                          else
0569                          {
0570                              $db->sql_query($query_first[1]);
0571                          }
0572                      }
0573                  }
0574   
0575                  if (!empty($schema['autoincrement']))
0576                  {
0577                      switch ($db->get_sql_layer())
0578                      {
0579                          case 'postgres':
0580                              $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'] . '));');
0581                              break;
0582   
0583                          case 'oracle':
0584                              $result = $db->sql_query('SELECT MAX(' . $schema['autoincrement'] . ') as max_id FROM ' . $schema['target']);
0585                              $row = $db->sql_fetchrow($result);
0586                              $db->sql_freeresult($result);
0587   
0588                              $largest_id = (int) $row['max_id'];
0589   
0590                              if ($largest_id)
0591                              {
0592                                  $db->sql_query('DROP SEQUENCE ' . $schema['target'] . '_seq');
0593                                  $db->sql_query('CREATE SEQUENCE ' . $schema['target'] . '_seq START WITH ' . ($largest_id + 1));
0594                              }
0595                              break;
0596                      }
0597                  }
0598              }
0599   
0600              // Process execute_always for this table
0601              // This is for code which needs to be executed on every pass of this table if
0602              // it gets split because of time restrictions
0603              if (!empty($schema['execute_always']))
0604              {
0605                  // @codingStandardsIgnoreStart
0606                  eval($schema['execute_always']);
0607                  // @codingStandardsIgnoreEnd
0608              }
0609   
0610              //
0611              // Set up some variables
0612              //
0613              // $waiting_rows    holds rows for multirows insertion (MySQL only)
0614              // $src_tables        holds unique tables with aliases to select from
0615              // $src_fields        will quickly refer source fields (or aliases) corresponding to the current index
0616              // $select_fields    holds the names of the fields to retrieve
0617              //
0618   
0619              $sql_data = array(
0620                  'source_fields'        => array(),
0621                  'target_fields'        => array(),
0622                  'source_tables'        => array(),
0623                  'select_fields'        => array(),
0624              );
0625   
0626              // This statement is building the keys for later insertion.
0627              $insert_query = $this->build_insert_query($schema, $sql_data, $current_table);
0628   
0629              // If no source table is affected, we skip the table
0630              if (empty($sql_data['source_tables']))
0631              {
0632                  $skip_rows = 0;
0633                  $current_table++;
0634                  continue;
0635              }
0636   
0637              $distinct = (!empty($schema['distinct'])) ? 'DISTINCT ' : '';
0638   
0639              $sql = 'SELECT ' . $distinct . implode(', ', $sql_data['select_fields']) . " \nFROM " . implode(', ', $sql_data['source_tables']);
0640   
0641              // Where
0642              $sql .= (!empty($schema['where'])) ? "\nWHERE (" . $schema['where'] . ')' : '';
0643   
0644              // Group By
0645              if (!empty($schema['group_by']))
0646              {
0647                  $schema['group_by'] = array($schema['group_by']);
0648                  foreach ($sql_data['select_fields'] as $select)
0649                  {
0650                      $alias = strpos(strtolower($select), ' as ');
0651                      $select = ($alias) ? substr($select, 0, $alias) : $select;
0652                      if (!in_array($select, $schema['group_by']))
0653                      {
0654                          $schema['group_by'][] = $select;
0655                      }
0656                  }
0657              }
0658              $sql .= (!empty($schema['group_by'])) ? "\nGROUP BY " . implode(', ', $schema['group_by']) : '';
0659   
0660              // Having
0661              $sql .= (!empty($schema['having'])) ? "\nHAVING " . $schema['having'] : '';
0662   
0663              // Order By
0664              if (empty($schema['order_by']) && !empty($schema['primary']))
0665              {
0666                  $schema['order_by'] = $schema['primary'];
0667              }
0668              $sql .= (!empty($schema['order_by'])) ? "\nORDER BY " . $schema['order_by'] : '';
0669   
0670              // Counting basically holds the amount of rows processed.
0671              $counting = -1;
0672              $batch_time = 0;
0673   
0674              while ($counting === -1 || ($counting >= $convert->batch_size && still_on_time()))
0675              {
0676                  $old_current_table = $current_table;
0677   
0678                  $rows = '';
0679                  $waiting_rows = array();
0680   
0681                  if (!empty($batch_time))
0682                  {
0683                      $mtime = explode(' ', microtime());
0684                      $mtime = $mtime[0] + $mtime[1];
0685                      $rows = ceil($counting/($mtime - $batch_time)) . " rows/s ($counting rows) | ";
0686                  }
0687   
0688                  $this->template->assign_block_vars('checks', array(
0689                      'TITLE'        => "skip_rows = $skip_rows",
0690                      'RESULT'    => $rows . ((defined('DEBUG') && function_exists('memory_get_usage')) ? ceil(memory_get_usage()/1024) . ' ' . $user->lang['KIB'] : ''),
0691                  ));
0692   
0693                  $mtime = explode(' ', microtime());
0694                  $batch_time = $mtime[0] + $mtime[1];
0695   
0696                  if ($convert->mysql_convert && $same_db)
0697                  {
0698                      $src_db->sql_query("SET NAMES 'binary'");
0699                  }
0700   
0701                  // Take skip rows into account and only fetch batch_size amount of rows
0702                  $___result = $src_db->sql_query_limit($sql, $convert->batch_size, $skip_rows);
0703   
0704                  if ($convert->mysql_convert && $same_db)
0705                  {
0706                      $src_db->sql_query("SET NAMES 'utf8'");
0707                  }
0708   
0709                  // This loop processes each row
0710                  $counting = 0;
0711   
0712                  $convert->row = $convert_row = array();
0713   
0714                  if (!empty($schema['autoincrement']))
0715                  {
0716                      switch ($db->get_sql_layer())
0717                      {
0718                          case 'mssql_odbc':
0719                          case 'mssqlnative':
0720                              $db->sql_query('SET IDENTITY_INSERT ' . $schema['target'] . ' ON');
0721                              break;
0722                      }
0723                  }
0724   
0725                  // Now handle the rows until time is over or no more rows to process...
0726                  while ($counting === 0 || still_on_time())
0727                  {
0728                      $convert_row = $src_db->sql_fetchrow($___result);
0729   
0730                      if (!$convert_row)
0731                      {
0732                          // move to the next batch or table
0733                          break;
0734                      }
0735   
0736                      // With this we are able to always save the last state
0737                      $convert->row = $convert_row;
0738   
0739                      // Increment the counting variable, it stores the number of rows we have processed
0740                      $counting++;
0741   
0742                      $insert_values = array();
0743   
0744                      $sql_flag = $this->process_row($schema, $sql_data, $insert_values);
0745   
0746                      if ($sql_flag === true)
0747                      {
0748                          switch ($db->get_sql_layer())
0749                          {
0750                              // If MySQL, we'll wait to have num_wait_rows rows to submit at once
0751                              case 'mysql':
0752                              case 'mysql4':
0753                              case 'mysqli':
0754                                  $waiting_rows[] = '(' . implode(', ', $insert_values) . ')';
0755   
0756                                  if (sizeof($waiting_rows) >= $convert->num_wait_rows)
0757                                  {
0758                                      $errored = false;
0759   
0760                                      $db->sql_return_on_error(true);
0761   
0762                                      if (!$db->sql_query($insert_query . implode(', ', $waiting_rows)))
0763                                      {
0764                                          $errored = true;
0765                                      }
0766                                      $db->sql_return_on_error(false);
0767   
0768                                      if ($errored)
0769                                      {
0770                                          $db->sql_return_on_error(true);
0771   
0772                                          // Because it errored out we will try to insert the rows one by one... most of the time this
0773                                          // is caused by duplicate entries - but we also do not want to miss one...
0774                                          foreach ($waiting_rows as $waiting_sql)
0775                                          {
0776                                              if (!$db->sql_query($insert_query . $waiting_sql))
0777                                              {
0778                                                  $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);
0779                                              }
0780                                          }
0781   
0782                                          $db->sql_return_on_error(false);
0783                                      }
0784   
0785                                      $waiting_rows = array();
0786                                  }
0787   
0788                                  break;
0789   
0790                              default:
0791                                  $insert_sql = $insert_query . '(' . implode(', ', $insert_values) . ')';
0792   
0793                                  $db->sql_return_on_error(true);
0794   
0795                                  if (!$db->sql_query($insert_sql))
0796                                  {
0797                                      $this->db_error($user->lang['DB_ERR_INSERT'], htmlspecialchars($insert_sql) . '<br /><br />' . htmlspecialchars(print_r($db->_sql_error(), true)), __LINE__, __FILE__, true);
0798                                  }
0799                                  $db->sql_return_on_error(false);
0800   
0801                                  $waiting_rows = array();
0802   
0803                                  break;
0804                          }
0805                      }
0806   
0807                      $skip_rows++;
0808                  }
0809                  $src_db->sql_freeresult($___result);
0810   
0811                  // We might still have some rows waiting
0812                  if (sizeof($waiting_rows))
0813                  {
0814                      $errored = false;
0815                      $db->sql_return_on_error(true);
0816   
0817                      if (!$db->sql_query($insert_query . implode(', ', $waiting_rows)))
0818                      {
0819                          $errored = true;
0820                      }
0821                      $db->sql_return_on_error(false);
0822   
0823                      if ($errored)
0824                      {
0825                          $db->sql_return_on_error(true);
0826   
0827                          // Because it errored out we will try to insert the rows one by one... most of the time this
0828                          // is caused by duplicate entries - but we also do not want to miss one...
0829                          foreach ($waiting_rows as $waiting_sql)
0830                          {
0831                              $db->sql_query($insert_query . $waiting_sql);
0832                              $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);
0833                          }
0834   
0835                          $db->sql_return_on_error(false);
0836                      }
0837   
0838                      $waiting_rows = array();
0839                  }
0840   
0841                  if (!empty($schema['autoincrement']))
0842                  {
0843                      switch ($db->get_sql_layer())
0844                      {
0845                          case 'mssql_odbc':
0846                          case 'mssqlnative':
0847                              $db->sql_query('SET IDENTITY_INSERT ' . $schema['target'] . ' OFF');
0848                              break;
0849   
0850                          case 'postgres':
0851                              $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'] . '));');
0852                              break;
0853   
0854                          case 'oracle':
0855                              $result = $db->sql_query('SELECT MAX(' . $schema['autoincrement'] . ') as max_id FROM ' . $schema['target']);
0856                              $row = $db->sql_fetchrow($result);
0857                              $db->sql_freeresult($result);
0858   
0859                              $largest_id = (int) $row['max_id'];
0860   
0861                              if ($largest_id)
0862                              {
0863                                  $db->sql_query('DROP SEQUENCE ' . $schema['target'] . '_seq');
0864                                  $db->sql_query('CREATE SEQUENCE ' . $schema['target'] . '_seq START WITH ' . ($largest_id + 1));
0865                              }
0866                              break;
0867                      }
0868                  }
0869              }
0870   
0871              // When we reach this point, either the current table has been processed or we're running out of time.
0872              if (still_on_time() && $counting < $convert->batch_size/* && !defined('DEBUG')*/)
0873              {
0874                  $skip_rows = 0;
0875                  $current_table++;
0876              }
0877              else
0878              {/*
0879                  if (still_on_time() && $counting < $convert->batch_size)
0880                  {
0881                      $skip_rows = 0;
0882                      $current_table++;
0883                  }*/
0884   
0885                  // Looks like we ran out of time.
0886                  $url = $this->save_convert_progress($converter, 'current_table=' . $current_table . '&amp;skip_rows=' . $skip_rows);
0887   
0888                  $current_table++;
0889  //                $percentage = ($skip_rows == 0) ? 0 : floor(100 / ($total_rows / $skip_rows));
0890   
0891                  $msg = sprintf($user->lang['STEP_PERCENT_COMPLETED'], $current_table, sizeof($convert->convertor['schema']));
0892   
0893                  $this->template->assign_vars(array(
0894                      'BODY'            => $msg,
0895                      'L_SUBMIT'        => $user->lang['CONTINUE_CONVERT'],
0896                      'U_ACTION'        => $url,
0897                  ));
0898   
0899                  $this->meta_refresh($url);
0900                  return;
0901              }
0902          }
0903   
0904          // Process execute_last then we'll be done
0905          $url = $this->save_convert_progress($converter, 'jump=1');
0906   
0907          $this->template->assign_vars(array(
0908              'L_SUBMIT'        => $user->lang['FINAL_STEP'],
0909              'U_ACTION'        => $url,
0910          ));
0911   
0912          $this->meta_refresh($url);
0913          return;
0914      }
0915   
0916      /**
0917       * Sync function being executed at the middle, some functions need to be executed after a successful sync.
0918       */
0919      function sync_forums($converter, $sync_batch)
0920      {
0921          global $user, $db, $phpbb_root_path, $phpEx, $config, $cache;
0922          global $convert;
0923   
0924          include_once ($phpbb_root_path . 'includes/functions_admin.' . $phpEx);
0925   
0926          $this->template->assign_block_vars('checks', array(
0927              'S_LEGEND'    => true,
0928              'LEGEND'    => $user->lang['SYNC_TOPICS'],
0929          ));
0930   
0931          $batch_size = $convert->batch_size;
0932   
0933          $sql = 'SELECT MIN(topic_id) as min_value, MAX(topic_id) AS max_value
0934              FROM ' . TOPICS_TABLE;
0935          $result = $db->sql_query($sql);
0936          $row = $db->sql_fetchrow($result);
0937          $db->sql_freeresult($result);
0938   
0939          // Set values of minimum/maximum primary value for this table.
0940          $primary_min = $row['min_value'];
0941          $primary_max = $row['max_value'];
0942   
0943          if ($sync_batch == 0)
0944          {
0945              $sync_batch = (int) $primary_min;
0946          }
0947   
0948          if ($sync_batch == 0)
0949          {
0950              $sync_batch = 1;
0951          }
0952   
0953          // Fetch a batch of rows, process and insert them.
0954          while ($sync_batch <= $primary_max && still_on_time())
0955          {
0956              $end = ($sync_batch + $batch_size - 1);
0957   
0958              // Sync all topics in batch mode...
0959              sync('topic', 'range', 'topic_id BETWEEN ' . $sync_batch . ' AND ' . $end, true, true);
0960   
0961              $this->template->assign_block_vars('checks', array(
0962                  'TITLE'        => sprintf($user->lang['SYNC_TOPIC_ID'], $sync_batch, ($sync_batch + $batch_size)) . ((defined('DEBUG') && function_exists('memory_get_usage')) ? ' [' . ceil(memory_get_usage()/1024) . ' ' . $user->lang['KIB'] . ']' : ''),
0963                  'RESULT'    => $user->lang['DONE'],
0964              ));
0965   
0966              $sync_batch += $batch_size;
0967          }
0968   
0969          if ($sync_batch >= $primary_max)
0970          {
0971              $url = $this->save_convert_progress($converter, 'final_jump=1');
0972   
0973              $this->template->assign_vars(array(
0974                  'L_SUBMIT'        => $user->lang['CONTINUE_CONVERT'],
0975                  'U_ACTION'        => $url,
0976              ));
0977   
0978              $this->meta_refresh($url);
0979              return;
0980          }
0981          else
0982          {
0983              $sync_batch--;
0984          }
0985   
0986          $url = $this->save_convert_progress($converter, 'sync_batch=' . $sync_batch);
0987   
0988          $this->template->assign_vars(array(
0989              'L_SUBMIT'        => $user->lang['CONTINUE_CONVERT'],
0990              'U_ACTION'        => $url,
0991          ));
0992   
0993          $this->meta_refresh($url);
0994          return;
0995      }
0996   
0997      /**
0998       * Save the convertor status
0999       */
1000      function save_convert_progress($convertor_tag, $step)
1001      {
1002          global $config, $convert, $language;
1003   
1004          // Save convertor Status
1005          $config->set('convert_progress', serialize(array(
1006              'step'            => $step,
1007              'table_prefix'    => $convert->src_table_prefix,
1008              'tag'            => $convert->convertor_tag,
1009          )), false);
1010   
1011          $config->set('convert_db_server', serialize(array(
1012              'dbms'            => $convert->src_dbms,
1013              'dbhost'        => $convert->src_dbhost,
1014              'dbport'        => $convert->src_dbport,
1015              'dbname'        => $convert->src_dbname,
1016          )), false);
1017   
1018          $config->set('convert_db_user', serialize(array(
1019              'dbuser'        => $convert->src_dbuser,
1020              'dbpasswd'        => $convert->src_dbpasswd,
1021          )), false);
1022   
1023          return $this->controller_helper->route('phpbb_convert_convert', array('converter' => $convertor_tag)) . '?' . $step;
1024      }
1025   
1026      /**
1027       * Finish conversion, the last function to be called.
1028       */
1029      function finish_conversion()
1030      {
1031          global $db, $phpbb_root_path, $phpEx, $convert, $config, $language, $user;
1032          global $cache, $auth, $phpbb_container, $phpbb_log;
1033   
1034          include_once ($phpbb_root_path . 'includes/functions_admin.' . $phpEx);
1035   
1036          $db->sql_query('DELETE FROM ' . CONFIG_TABLE . "
1037              WHERE config_name = 'convert_progress'
1038                  OR config_name = 'convert_options'
1039                  OR config_name = 'convert_db_server'
1040                  OR config_name = 'convert_db_user'");
1041          $db->sql_query('DELETE FROM ' . SESSIONS_TABLE);
1042   
1043          @unlink($phpbb_container->getParameter('core.cache_dir') . 'data_global.' . $phpEx);
1044          phpbb_cache_moderators($db, $cache, $auth);
1045   
1046          // And finally, add a note to the log
1047          $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_INSTALL_CONVERTED', false, array($convert->convertor_data['forum_name'], $config['version']));
1048   
1049          $url = $this->controller_helper->route('phpbb_convert_finish');
1050   
1051          $this->template->assign_vars(array(
1052              'L_SUBMIT'        => $user->lang['FINAL_STEP'],
1053              'U_ACTION'        => $url,
1054          ));
1055   
1056          $this->meta_refresh($url);
1057          return;
1058      }
1059   
1060      /**
1061       * This function marks the steps after syncing
1062       */
1063      function final_jump($final_jump)
1064      {
1065          global $user, $src_db, $same_db, $db, $phpbb_root_path, $phpEx, $config, $cache;
1066          global $convert;
1067   
1068          $this->template->assign_block_vars('checks', array(
1069              'S_LEGEND'    => true,
1070              'LEGEND'    => $user->lang['PROCESS_LAST'],
1071          ));
1072   
1073          if ($final_jump == 1)
1074          {
1075              $db->sql_return_on_error(true);
1076   
1077              update_topics_posted();
1078   
1079              $this->template->assign_block_vars('checks', array(
1080                  'TITLE'        => $user->lang['UPDATE_TOPICS_POSTED'],
1081                  'RESULT'    => $user->lang['DONE'],
1082              ));
1083   
1084              if ($db->get_sql_error_triggered())
1085              {
1086                  $this->template->assign_vars(array(
1087                      'S_ERROR_BOX'    => true,
1088                      'ERROR_TITLE'    => $user->lang['UPDATE_TOPICS_POSTED'],
1089                      'ERROR_MSG'        => $user->lang['UPDATE_TOPICS_POSTED_ERR'],
1090                  ));
1091              }
1092              $db->sql_return_on_error(false);
1093   
1094              $this->finish_conversion();
1095              return;
1096          }
1097      }
1098   
1099      /**
1100       * This function marks the steps before syncing (jump=1)
1101       */
1102      function jump($converter, $jump, $last_statement)
1103      {
1104          /** @var \phpbb\db\driver\driver_interface $src_db */
1105          /** @var \phpbb\cache\driver\driver_interface $cache */
1106          global $user, $src_db, $same_db, $db, $phpbb_root_path, $phpEx, $config, $cache;
1107          global $convert;
1108   
1109          include_once ($phpbb_root_path . 'includes/functions_admin.' . $phpEx);
1110   
1111          $this->template->assign_block_vars('checks', array(
1112              'S_LEGEND'    => true,
1113              'LEGEND'    => $user->lang['PROCESS_LAST'],
1114          ));
1115   
1116          if ($jump == 1)
1117          {
1118              // Execute 'last' statements/queries
1119              if (!empty($convert->convertor['execute_last']))
1120              {
1121                  if (!is_array($convert->convertor['execute_last']))
1122                  {
1123                      // @codingStandardsIgnoreStart
1124                      eval($convert->convertor['execute_last']);
1125                      // @codingStandardsIgnoreEnd
1126                  }
1127                  else
1128                  {
1129                      while ($last_statement < sizeof($convert->convertor['execute_last']))
1130                      {
1131                          // @codingStandardsIgnoreStart
1132                          eval($convert->convertor['execute_last'][$last_statement]);
1133                          // @codingStandardsIgnoreEnd
1134   
1135                          $this->template->assign_block_vars('checks', array(
1136                              'TITLE'        => $convert->convertor['execute_last'][$last_statement],
1137                              'RESULT'    => $user->lang['DONE'],
1138                          ));
1139   
1140                          $last_statement++;
1141                          $url = $this->save_convert_progress($converter, 'jump=1&amp;last=' . $last_statement);
1142   
1143                          $percentage = ($last_statement == 0) ? 0 : floor(100 / (sizeof($convert->convertor['execute_last']) / $last_statement));
1144                          $msg = sprintf($user->lang['STEP_PERCENT_COMPLETED'], $last_statement, sizeof($convert->convertor['execute_last']), $percentage);
1145   
1146                          $this->template->assign_vars(array(
1147                              'L_SUBMIT'        => $user->lang['CONTINUE_LAST'],
1148                              'BODY'            => $msg,
1149                              'U_ACTION'        => $url,
1150                          ));
1151   
1152                          $this->meta_refresh($url);
1153                          return;
1154                      }
1155                  }
1156              }
1157   
1158              if (!empty($convert->convertor['query_last']))
1159              {
1160                  if (!is_array($convert->convertor['query_last']))
1161                  {
1162                      $convert->convertor['query_last'] = array('target', array($convert->convertor['query_last']));
1163                  }
1164                  else if (!is_array($convert->convertor['query_last'][0]))
1165                  {
1166                      $convert->convertor['query_last'] = array(array($convert->convertor['query_last'][0], $convert->convertor['query_last'][1]));
1167                  }
1168   
1169                  foreach ($convert->convertor['query_last'] as $query_last)
1170                  {
1171                      if ($query_last[0] == 'src')
1172                      {
1173                          if ($convert->mysql_convert && $same_db)
1174                          {
1175                              $src_db->sql_query("SET NAMES 'binary'");
1176                          }
1177   
1178                          $src_db->sql_query($query_last[1]);
1179   
1180                          if ($convert->mysql_convert && $same_db)
1181                          {
1182                              $src_db->sql_query("SET NAMES 'utf8'");
1183                          }
1184                      }
1185                      else
1186                      {
1187                          $db->sql_query($query_last[1]);
1188                      }
1189                  }
1190              }
1191   
1192              // Sanity check
1193              $db->sql_return_on_error(false);
1194              $src_db->sql_return_on_error(false);
1195   
1196              fix_empty_primary_groups();
1197   
1198              $sql = 'SELECT MIN(user_regdate) AS board_startdate
1199                  FROM ' . USERS_TABLE;
1200              $result = $db->sql_query($sql);
1201              $row = $db->sql_fetchrow($result);
1202              $db->sql_freeresult($result);
1203   
1204              if (!isset($config['board_startdate']) || ($row['board_startdate'] < $config['board_startdate'] && $row['board_startdate'] > 0))
1205              {
1206                  $config->set('board_startdate', $row['board_startdate']);
1207                  $db->sql_query('UPDATE ' . USERS_TABLE . ' SET user_regdate = ' . $row['board_startdate'] . ' WHERE user_id = ' . ANONYMOUS);
1208              }
1209   
1210              update_dynamic_config();
1211   
1212              $this->template->assign_block_vars('checks', array(
1213                  'TITLE'        => $user->lang['CLEAN_VERIFY'],
1214                  'RESULT'    => $user->lang['DONE'],
1215              ));
1216   
1217              $url = $this->save_convert_progress($converter, 'jump=2');
1218   
1219              $this->template->assign_vars(array(
1220                  'L_SUBMIT'        => $user->lang['CONTINUE_CONVERT'],
1221                  'U_ACTION'        => $url,
1222              ));
1223   
1224              $this->meta_refresh($url);
1225              return;
1226          }
1227   
1228          if ($jump == 2)
1229          {
1230              $db->sql_query('UPDATE ' . USERS_TABLE . " SET user_permissions = ''");
1231   
1232              // TODO: sync() is likely going to bomb out on forums with a considerable amount of topics.
1233              // TODO: the sync function is able to handle FROM-TO values, we should use them here (batch processing)
1234              sync('forum', '', '', false, true);
1235              $cache->destroy('sql', FORUMS_TABLE);
1236   
1237              $this->template->assign_block_vars('checks', array(
1238                  'TITLE'        => $user->lang['SYNC_FORUMS'],
1239                  'RESULT'    => $user->lang['DONE'],
1240              ));
1241   
1242              // Continue with synchronizing the forums...
1243              $url = $this->save_convert_progress($converter, 'sync_batch=0');
1244   
1245              $this->template->assign_vars(array(
1246                  'L_SUBMIT'        => $user->lang['CONTINUE_CONVERT'],
1247                  'U_ACTION'        => $url,
1248              ));
1249   
1250              $this->meta_refresh($url);
1251              return;
1252          }
1253      }
1254   
1255      function build_insert_query(&$schema, &$sql_data, $current_table)
1256      {
1257          global $db, $user;
1258          global $convert;
1259   
1260          // Can we use IGNORE with this DBMS?
1261          $sql_ignore = (strpos($db->get_sql_layer(), 'mysql') === 0 && !defined('DEBUG')) ? 'IGNORE ' : '';
1262          $insert_query = 'INSERT ' . $sql_ignore . 'INTO ' . $schema['target'] . ' (';
1263   
1264          $aliases = array();
1265   
1266          $sql_data = array(
1267              'source_fields'        => array(),
1268              'target_fields'        => array(),
1269              'source_tables'        => array(),
1270              'select_fields'        => array(),
1271          );
1272   
1273          foreach ($schema as $key => $val)
1274          {
1275              // Example: array('group_name',                'extension_groups.group_name',        'htmlspecialchars'),
1276              if (is_int($key))
1277              {
1278                  if (!empty($val[0]))
1279                  {
1280                      // Target fields
1281                      $sql_data['target_fields'][$val[0]] = $key;
1282                      $insert_query .= $val[0] . ', ';
1283                  }
1284   
1285                  if (!is_array($val[1]))
1286                  {
1287                      $val[1] = array($val[1]);
1288                  }
1289   
1290                  foreach ($val[1] as $valkey => $value_1)
1291                  {
1292                      // This should cover about any case:
1293                      //
1294                      // table.field                    => SELECT table.field                FROM table
1295                      // table.field AS alias            => SELECT table.field    AS alias    FROM table
1296                      // table.field AS table2.alias    => SELECT table2.field    AS alias    FROM table table2
1297                      // table.field AS table2.field    => SELECT table2.field                FROM table table2
1298                      //
1299                      if (preg_match('/^([a-z0-9_]+)\.([a-z0-9_]+)( +AS +(([a-z0-9_]+?)\.)?([a-z0-9_]+))?$/i', $value_1, $m))
1300                      {
1301                          // There is 'AS ...' in the field names
1302                          if (!empty($m[3]))
1303                          {
1304                              $value_1 = ($m[2] == $m[6]) ? $m[1] . '.' . $m[2] : $m[1] . '.' . $m[2] . ' AS ' . $m[6];
1305   
1306                              // Table alias: store it then replace the source table with it
1307                              if (!empty($m[5]) && $m[5] != $m[1])
1308                              {
1309                                  $aliases[$m[5]] = $m[1];
1310                                  $value_1 = str_replace($m[1] . '.' . $m[2], $m[5] . '.' . $m[2], $value_1);
1311                              }
1312                          }
1313                          else
1314                          {
1315                              // No table alias
1316                              $sql_data['source_tables'][$m[1]] = (empty($convert->src_table_prefix)) ? $m[1] : $convert->src_table_prefix . $m[1] . ' ' . $m[1];
1317                          }
1318   
1319                          $sql_data['select_fields'][$value_1] = $value_1;
1320                          $sql_data['source_fields'][$key][$valkey] = (!empty($m[6])) ? $m[6] : $m[2];
1321                      }
1322                  }
1323              }
1324              else if ($key == 'where' || $key == 'group_by' || $key == 'order_by' || $key == 'having')
1325              {
1326                  if (@preg_match_all('/([a-z0-9_]+)\.([a-z0-9_]+)/i', $val, $m))
1327                  {
1328                      foreach ($m[1] as $value)
1329                      {
1330                          $sql_data['source_tables'][$value] = (empty($convert->src_table_prefix)) ? $value : $convert->src_table_prefix . $value . ' ' . $value;
1331                      }
1332                  }
1333              }
1334          }
1335   
1336          // Add the aliases to the list of tables
1337          foreach ($aliases as $alias => $table)
1338          {
1339              $sql_data['source_tables'][$alias] = $convert->src_table_prefix . $table . ' ' . $alias;
1340          }
1341   
1342          // 'left_join'        => 'forums LEFT JOIN forum_prune ON forums.forum_id = forum_prune.forum_id',
1343          if (!empty($schema['left_join']))
1344          {
1345              if (!is_array($schema['left_join']))
1346              {
1347                  $schema['left_join'] = array($schema['left_join']);
1348              }
1349   
1350              foreach ($schema['left_join'] as $left_join)
1351              {
1352                  // This won't handle concatened LEFT JOINs
1353                  if (!preg_match('/([a-z0-9_]+) LEFT JOIN ([a-z0-9_]+) A?S? ?([a-z0-9_]*?) ?(ON|USING)(.*)/i', $left_join, $m))
1354                  {
1355                      $this->error(sprintf($user->lang['NOT_UNDERSTAND'], 'LEFT JOIN', $left_join, $current_table, $schema['target']), __LINE__, __FILE__);
1356                  }
1357   
1358                  if (!empty($aliases[$m[2]]))
1359                  {
1360                      if (!empty($m[3]))
1361                      {
1362                          $this->error(sprintf($user->lang['NAMING_CONFLICT'], $m[2], $m[3], $schema['left_join']), __LINE__, __FILE__);
1363                      }
1364   
1365                      $m[2] = $aliases[$m[2]];
1366                      $m[3] = $m[2];
1367                  }
1368   
1369                  $right_table = $convert->src_table_prefix . $m[2];
1370                  if (!empty($m[3]))
1371                  {
1372                      unset($sql_data['source_tables'][$m[3]]);
1373                  }
1374                  else if ($m[2] != $m[1])
1375                  {
1376                      unset($sql_data['source_tables'][$m[2]]);
1377                  }
1378   
1379                  if (strpos($sql_data['source_tables'][$m[1]], "\nLEFT JOIN") !== false)
1380                  {
1381                      $sql_data['source_tables'][$m[1]] = '(' . $sql_data['source_tables'][$m[1]] . ")\nLEFT JOIN $right_table";
1382                  }
1383                  else
1384                  {
1385                      $sql_data['source_tables'][$m[1]] .= "\nLEFT JOIN $right_table";
1386                  }
1387   
1388                  if (!empty($m[3]))
1389                  {
1390                      unset($sql_data['source_tables'][$m[3]]);
1391                      $sql_data['source_tables'][$m[1]] .= ' AS ' . $m[3];
1392                  }
1393                  else if (!empty($convert->src_table_prefix))
1394                  {
1395                      $sql_data['source_tables'][$m[1]] .= ' AS ' . $m[2];
1396                  }
1397                  $sql_data['source_tables'][$m[1]] .= ' ' . $m[4] . $m[5];
1398              }
1399          }
1400   
1401          // Remove ", " from the end of the insert query
1402          $insert_query = substr($insert_query, 0, -2) . ') VALUES ';
1403   
1404          return $insert_query;
1405      }
1406   
1407      /**
1408       * Function for processing the currently handled row
1409       */
1410      function process_row(&$schema, &$sql_data, &$insert_values)
1411      {
1412          global $user, $phpbb_root_path, $phpEx, $db, $lang, $config, $cache;
1413          global $convert, $convert_row;
1414   
1415          $sql_flag = false;
1416   
1417          foreach ($schema as $key => $fields)
1418          {
1419              // We are only interested in the lines with:
1420              // array('comment', 'attachments_desc.comment', 'htmlspecialchars'),
1421              if (is_int($key))
1422              {
1423                  if (!is_array($fields[1]))
1424                  {
1425                      $fields[1] = array($fields[1]);
1426                  }
1427   
1428                  $firstkey_set = false;
1429                  $firstkey = 0;
1430   
1431                  foreach ($fields[1] as $inner_key => $inner_value)
1432                  {
1433                      if (!$firstkey_set)
1434                      {
1435                          $firstkey = $inner_key;
1436                          $firstkey_set = true;
1437                      }
1438   
1439                      $src_field = isset($sql_data['source_fields'][$key][$inner_key]) ? $sql_data['source_fields'][$key][$inner_key] : '';
1440   
1441                      if (!empty($src_field))
1442                      {
1443                          $fields[1][$inner_key] = $convert->row[$src_field];
1444                      }
1445                  }
1446   
1447                  if (!empty($fields[0]))
1448                  {
1449                      // We have a target field, if we haven't set $sql_flag yet it will be set to TRUE.
1450                      // If a function has already set it to FALSE it won't change it.
1451                      if ($sql_flag === false)
1452                      {
1453                          $sql_flag = true;
1454                      }
1455   
1456                      // No function assigned?
1457                      if (empty($fields[2]))
1458                      {
1459                          $value = $fields[1][$firstkey];
1460                      }
1461                      else if (is_array($fields[2]) && !is_callable($fields[2]))
1462                      {
1463                          // Execute complex function/eval/typecast
1464                          $value = $fields[1];
1465   
1466                          foreach ($fields[2] as $type => $execution)
1467                          {
1468                              if (strpos($type, 'typecast') === 0)
1469                              {
1470                                  if (!is_array($value))
1471                                  {
1472                                      $value = array($value);
1473                                  }
1474                                  $value = $value[0];
1475                                  settype($value, $execution);
1476                              }
1477                              else if (strpos($type, 'function') === 0)
1478                              {
1479                                  if (!is_array($value))
1480                                  {
1481                                      $value = array($value);
1482                                  }
1483   
1484                                  $value = call_user_func_array($execution, $value);
1485                              }
1486                              else if (strpos($type, 'execute') === 0)
1487                              {
1488                                  if (!is_array($value))
1489                                  {
1490                                      $value = array($value);
1491                                  }
1492   
1493                                  $execution = str_replace('{RESULT}', '$value', $execution);
1494                                  $execution = str_replace('{VALUE}', '$value', $execution);
1495                                  // @codingStandardsIgnoreStart
1496                                  eval($execution);
1497                                  // @codingStandardsIgnoreEnd
1498                              }
1499                          }
1500                      }
1501                      else
1502                      {
1503                          $value = call_user_func_array($fields[2], $fields[1]);
1504                      }
1505   
1506                      if (is_null($value))
1507                      {
1508                          $value = '';
1509                      }
1510   
1511                      $insert_values[] = $db->_sql_validate_value($value);
1512                  }
1513                  else if (!empty($fields[2]))
1514                  {
1515                      if (is_array($fields[2]))
1516                      {
1517                          // Execute complex function/eval/typecast
1518                          $value = '';
1519   
1520                          foreach ($fields[2] as $type => $execution)
1521                          {
1522                              if (strpos($type, 'typecast') === 0)
1523                              {
1524                                  $value = settype($value, $execution);
1525                              }
1526                              else if (strpos($type, 'function') === 0)
1527                              {
1528                                  if (!is_array($value))
1529                                  {
1530                                      $value = array($value);
1531                                  }
1532   
1533                                  $value = call_user_func_array($execution, $value);
1534                              }
1535                              else if (strpos($type, 'execute') === 0)
1536                              {
1537                                  if (!is_array($value))
1538                                  {
1539                                      $value = array($value);
1540                                  }
1541   
1542                                  $execution = str_replace('{RESULT}', '$value', $execution);
1543                                  $execution = str_replace('{VALUE}', '$value', $execution);
1544                                  // @codingStandardsIgnoreStart
1545                                  eval($execution);
1546                                  // @codingStandardsIgnoreEnd
1547                              }
1548                          }
1549                      }
1550                      else
1551                      {
1552                          call_user_func_array($fields[2], $fields[1]);
1553                      }
1554                  }
1555              }
1556          }
1557   
1558          return $sql_flag;
1559      }
1560   
1561      /**
1562       * Own meta refresh function to be able to change the global time used
1563       */
1564      function meta_refresh($url)
1565      {
1566          global $convert;
1567   
1568          if ($convert->options['refresh'])
1569          {
1570              // Because we should not rely on correct settings, we simply use the relative path here directly.
1571              $this->template->assign_vars(array(
1572                      'S_REFRESH'    => true,
1573                      'META'        => '<meta http-equiv="refresh" content="5; url=' . $url . '" />')
1574              );
1575          }
1576      }
1577   
1578      /**
1579       * Error handler function
1580       *
1581       * This function needs to be kept for BC
1582       *
1583       * @param $error
1584       * @param $line
1585       * @param $file
1586       * @param bool|false $skip
1587       */
1588      public function error($error, $line, $file, $skip = false)
1589      {
1590          $this->template->assign_block_vars('errors', array(
1591              'TITLE'    => $error,
1592              'DESCRIPTION' => 'In ' . $file . ' on line ' . $line,
1593          ));
1594      }
1595   
1596      /**
1597       * Database error handler function
1598       *
1599       * This function needs to be kept for BC
1600       *
1601       * @param $error
1602       * @param $sql
1603       * @param $line
1604       * @param $file
1605       * @param bool|false $skip
1606       */
1607      public function db_error($error, $sql, $line, $file, $skip = false)
1608      {
1609          $this->template->assign_block_vars('errors', array(
1610              'TITLE'    => $error,
1611              'DESCRIPTION' => 'In ' . $file . ' on line ' . $line . '<br /><br /><strong>SQL:</strong> ' . $sql,
1612          ));
1613      }
1614  }
1615