Verzeichnisstruktur phpBB-3.3.15


Veröffentlicht
28.08.2024

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