"Fossies" - the Fresh Open Source Software Archive

Member "phpMyAdmin-5.1.0-english/libraries/classes/Plugins/Export/ExportSql.php" (24 Feb 2021, 105440 Bytes) of package /linux/www/phpMyAdmin-5.1.0-english.zip:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) PHP source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "ExportSql.php" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 5.0.4-english_vs_5.1.0-english.

    1 <?php
    2 /**
    3  * Set of functions used to build SQL dumps of tables
    4  */
    5 
    6 declare(strict_types=1);
    7 
    8 namespace PhpMyAdmin\Plugins\Export;
    9 
   10 use PhpMyAdmin\Charsets;
   11 use PhpMyAdmin\DatabaseInterface;
   12 use PhpMyAdmin\Plugins\ExportPlugin;
   13 use PhpMyAdmin\Properties\Options\Groups\OptionsPropertyMainGroup;
   14 use PhpMyAdmin\Properties\Options\Groups\OptionsPropertyRootGroup;
   15 use PhpMyAdmin\Properties\Options\Groups\OptionsPropertySubgroup;
   16 use PhpMyAdmin\Properties\Options\Items\BoolPropertyItem;
   17 use PhpMyAdmin\Properties\Options\Items\MessageOnlyPropertyItem;
   18 use PhpMyAdmin\Properties\Options\Items\NumberPropertyItem;
   19 use PhpMyAdmin\Properties\Options\Items\RadioPropertyItem;
   20 use PhpMyAdmin\Properties\Options\Items\SelectPropertyItem;
   21 use PhpMyAdmin\Properties\Options\Items\TextPropertyItem;
   22 use PhpMyAdmin\Properties\Plugins\ExportPluginProperties;
   23 use PhpMyAdmin\SqlParser\Components\CreateDefinition;
   24 use PhpMyAdmin\SqlParser\Context;
   25 use PhpMyAdmin\SqlParser\Parser;
   26 use PhpMyAdmin\SqlParser\Statements\CreateStatement;
   27 use PhpMyAdmin\SqlParser\Token;
   28 use PhpMyAdmin\Util;
   29 use const E_USER_ERROR;
   30 use const PHP_VERSION;
   31 use function bin2hex;
   32 use function count;
   33 use function defined;
   34 use function explode;
   35 use function implode;
   36 use function in_array;
   37 use function intval;
   38 use function is_array;
   39 use function mb_strlen;
   40 use function mb_strpos;
   41 use function mb_substr;
   42 use function preg_quote;
   43 use function preg_replace;
   44 use function preg_split;
   45 use function sprintf;
   46 use function str_repeat;
   47 use function str_replace;
   48 use function stripos;
   49 use function strtotime;
   50 use function strtoupper;
   51 use function trigger_error;
   52 
   53 /**
   54  * Handles the export for the SQL class
   55  */
   56 class ExportSql extends ExportPlugin
   57 {
   58     /**
   59      * Whether charset header was sent.
   60      *
   61      * @var bool
   62      */
   63     private $sentCharset = false;
   64 
   65     public function __construct()
   66     {
   67         parent::__construct();
   68         $this->setProperties();
   69 
   70         // Avoids undefined variables, use NULL so isset() returns false
   71         if (isset($GLOBALS['sql_backquotes'])) {
   72             return;
   73         }
   74 
   75         $GLOBALS['sql_backquotes'] = null;
   76     }
   77 
   78     /**
   79      * Sets the export SQL properties
   80      *
   81      * @return void
   82      */
   83     protected function setProperties()
   84     {
   85         global $plugin_param, $dbi;
   86 
   87         $hide_sql = false;
   88         $hide_structure = false;
   89         if ($plugin_param['export_type'] === 'table'
   90             && ! $plugin_param['single_table']
   91         ) {
   92             $hide_structure = true;
   93             $hide_sql = true;
   94         }
   95 
   96         // In case we have `raw_query` parameter set,
   97         // we initialize SQL option
   98         if (isset($_REQUEST['raw_query'])) {
   99             $hide_structure = false;
  100             $hide_sql = false;
  101         }
  102 
  103         if ($hide_sql) {
  104             return;
  105         }
  106 
  107         $exportPluginProperties = new ExportPluginProperties();
  108         $exportPluginProperties->setText('SQL');
  109         $exportPluginProperties->setExtension('sql');
  110         $exportPluginProperties->setMimeType('text/x-sql');
  111         $exportPluginProperties->setOptionsText(__('Options'));
  112 
  113         // create the root group that will be the options field for
  114         // $exportPluginProperties
  115         // this will be shown as "Format specific options"
  116         $exportSpecificOptions = new OptionsPropertyRootGroup(
  117             'Format Specific Options'
  118         );
  119 
  120         // general options main group
  121         $generalOptions = new OptionsPropertyMainGroup('general_opts');
  122 
  123         // comments
  124         $subgroup = new OptionsPropertySubgroup('include_comments');
  125         $leaf = new BoolPropertyItem(
  126             'include_comments',
  127             __(
  128                 'Display comments <i>(includes info such as export'
  129                 . ' timestamp, PHP version, and server version)</i>'
  130             )
  131         );
  132         $subgroup->setSubgroupHeader($leaf);
  133 
  134         $leaf = new TextPropertyItem(
  135             'header_comment',
  136             __('Additional custom header comment (\n splits lines):')
  137         );
  138         $subgroup->addProperty($leaf);
  139         $leaf = new BoolPropertyItem(
  140             'dates',
  141             __(
  142                 'Include a timestamp of when databases were created, last'
  143                 . ' updated, and last checked'
  144             )
  145         );
  146         $subgroup->addProperty($leaf);
  147         if (! empty($GLOBALS['cfgRelation']['relation'])) {
  148             $leaf = new BoolPropertyItem(
  149                 'relation',
  150                 __('Display foreign key relationships')
  151             );
  152             $subgroup->addProperty($leaf);
  153         }
  154         if (! empty($GLOBALS['cfgRelation']['mimework'])) {
  155             $leaf = new BoolPropertyItem(
  156                 'mime',
  157                 __('Display media types')
  158             );
  159             $subgroup->addProperty($leaf);
  160         }
  161         $generalOptions->addProperty($subgroup);
  162 
  163         // enclose in a transaction
  164         $leaf = new BoolPropertyItem(
  165             'use_transaction',
  166             __('Enclose export in a transaction')
  167         );
  168         $leaf->setDoc(
  169             [
  170                 'programs',
  171                 'mysqldump',
  172                 'option_mysqldump_single-transaction',
  173             ]
  174         );
  175         $generalOptions->addProperty($leaf);
  176 
  177         // disable foreign key checks
  178         $leaf = new BoolPropertyItem(
  179             'disable_fk',
  180             __('Disable foreign key checks')
  181         );
  182         $leaf->setDoc(
  183             [
  184                 'manual_MySQL_Database_Administration',
  185                 'server-system-variables',
  186                 'sysvar_foreign_key_checks',
  187             ]
  188         );
  189         $generalOptions->addProperty($leaf);
  190 
  191         // export views as tables
  192         $leaf = new BoolPropertyItem(
  193             'views_as_tables',
  194             __('Export views as tables')
  195         );
  196         $generalOptions->addProperty($leaf);
  197 
  198         // export metadata
  199         $leaf = new BoolPropertyItem(
  200             'metadata',
  201             __('Export metadata')
  202         );
  203         $generalOptions->addProperty($leaf);
  204 
  205         // compatibility maximization
  206         $compats = $dbi->getCompatibilities();
  207         if (count($compats) > 0) {
  208             $values = [];
  209             foreach ($compats as $val) {
  210                 $values[$val] = $val;
  211             }
  212 
  213             $leaf = new SelectPropertyItem(
  214                 'compatibility',
  215                 __(
  216                     'Database system or older MySQL server to maximize output'
  217                     . ' compatibility with:'
  218                 )
  219             );
  220             $leaf->setValues($values);
  221             $leaf->setDoc(
  222                 [
  223                     'manual_MySQL_Database_Administration',
  224                     'Server_SQL_mode',
  225                 ]
  226             );
  227             $generalOptions->addProperty($leaf);
  228 
  229             unset($values);
  230         }
  231 
  232         // what to dump (structure/data/both)
  233         $subgroup = new OptionsPropertySubgroup(
  234             'dump_table',
  235             __('Dump table')
  236         );
  237         $leaf = new RadioPropertyItem('structure_or_data');
  238         $leaf->setValues(
  239             [
  240                 'structure'          => __('structure'),
  241                 'data'               => __('data'),
  242                 'structure_and_data' => __('structure and data'),
  243             ]
  244         );
  245         $subgroup->setSubgroupHeader($leaf);
  246         $generalOptions->addProperty($subgroup);
  247 
  248         // add the main group to the root group
  249         $exportSpecificOptions->addProperty($generalOptions);
  250 
  251         // structure options main group
  252         if (! $hide_structure) {
  253             $structureOptions = new OptionsPropertyMainGroup(
  254                 'structure',
  255                 __('Object creation options')
  256             );
  257             $structureOptions->setForce('data');
  258 
  259             // begin SQL Statements
  260             $subgroup = new OptionsPropertySubgroup();
  261             $leaf = new MessageOnlyPropertyItem(
  262                 'add_statements',
  263                 __('Add statements:')
  264             );
  265             $subgroup->setSubgroupHeader($leaf);
  266 
  267             // server export options
  268             if ($plugin_param['export_type'] === 'server') {
  269                 $leaf = new BoolPropertyItem(
  270                     'drop_database',
  271                     sprintf(__('Add %s statement'), '<code>DROP DATABASE IF EXISTS</code>')
  272                 );
  273                 $subgroup->addProperty($leaf);
  274             }
  275 
  276             if ($plugin_param['export_type'] === 'database') {
  277                 $create_clause = '<code>CREATE DATABASE / USE</code>';
  278                 $leaf = new BoolPropertyItem(
  279                     'create_database',
  280                     sprintf(__('Add %s statement'), $create_clause)
  281                 );
  282                 $subgroup->addProperty($leaf);
  283             }
  284 
  285             if ($plugin_param['export_type'] === 'table') {
  286                 $drop_clause = $dbi->getTable(
  287                     $GLOBALS['db'],
  288                     $GLOBALS['table']
  289                 )->isView()
  290                     ? '<code>DROP VIEW</code>'
  291                     : '<code>DROP TABLE</code>';
  292             } else {
  293                 $drop_clause = '<code>DROP TABLE / VIEW / PROCEDURE'
  294                     . ' / FUNCTION / EVENT</code>';
  295             }
  296 
  297             $drop_clause .= '<code> / TRIGGER</code>';
  298 
  299             $leaf = new BoolPropertyItem(
  300                 'drop_table',
  301                 sprintf(__('Add %s statement'), $drop_clause)
  302             );
  303             $subgroup->addProperty($leaf);
  304 
  305             $subgroup_create_table = new OptionsPropertySubgroup();
  306 
  307             // Add table structure option
  308             $leaf = new BoolPropertyItem(
  309                 'create_table',
  310                 sprintf(__('Add %s statement'), '<code>CREATE TABLE</code>')
  311             );
  312             $subgroup_create_table->setSubgroupHeader($leaf);
  313 
  314             $leaf = new BoolPropertyItem(
  315                 'if_not_exists',
  316                 '<code>IF NOT EXISTS</code> ' . __(
  317                     '(less efficient as indexes will be generated during table '
  318                     . 'creation)'
  319                 )
  320             );
  321             $subgroup_create_table->addProperty($leaf);
  322 
  323             $leaf = new BoolPropertyItem(
  324                 'auto_increment',
  325                 sprintf(__('%s value'), '<code>AUTO_INCREMENT</code>')
  326             );
  327             $subgroup_create_table->addProperty($leaf);
  328 
  329             $subgroup->addProperty($subgroup_create_table);
  330 
  331             // Add view option
  332             $subgroup_create_view = new OptionsPropertySubgroup();
  333             $leaf = new BoolPropertyItem(
  334                 'create_view',
  335                 sprintf(__('Add %s statement'), '<code>CREATE VIEW</code>')
  336             );
  337             $subgroup_create_view->setSubgroupHeader($leaf);
  338 
  339             $leaf = new BoolPropertyItem(
  340                 'simple_view_export',
  341                 /* l10n: Allow simplifying exported view syntax to only "CREATE VIEW" */
  342                 __('Use simple view export')
  343             );
  344             $subgroup_create_view->addProperty($leaf);
  345 
  346             $leaf = new BoolPropertyItem(
  347                 'view_current_user',
  348                 __('Exclude definition of current user')
  349             );
  350             $subgroup_create_view->addProperty($leaf);
  351 
  352             $leaf = new BoolPropertyItem(
  353                 'or_replace_view',
  354                 sprintf(__('%s view'), '<code>OR REPLACE</code>')
  355             );
  356             $subgroup_create_view->addProperty($leaf);
  357 
  358             $subgroup->addProperty($subgroup_create_view);
  359 
  360             $leaf = new BoolPropertyItem(
  361                 'procedure_function',
  362                 sprintf(
  363                     __('Add %s statement'),
  364                     '<code>CREATE PROCEDURE / FUNCTION / EVENT</code>'
  365                 )
  366             );
  367             $subgroup->addProperty($leaf);
  368 
  369             // Add triggers option
  370             $leaf = new BoolPropertyItem(
  371                 'create_trigger',
  372                 sprintf(__('Add %s statement'), '<code>CREATE TRIGGER</code>')
  373             );
  374             $subgroup->addProperty($leaf);
  375 
  376             $structureOptions->addProperty($subgroup);
  377 
  378             $leaf = new BoolPropertyItem(
  379                 'backquotes',
  380                 __(
  381                     'Enclose table and column names with backquotes '
  382                     . '<i>(Protects column and table names formed with'
  383                     . ' special characters or keywords)</i>'
  384                 )
  385             );
  386 
  387             $structureOptions->addProperty($leaf);
  388 
  389             // add the main group to the root group
  390             $exportSpecificOptions->addProperty($structureOptions);
  391         }
  392 
  393         // begin Data options
  394         $dataOptions = new OptionsPropertyMainGroup(
  395             'data',
  396             __('Data creation options')
  397         );
  398         $dataOptions->setForce('structure');
  399         $leaf = new BoolPropertyItem(
  400             'truncate',
  401             __('Truncate table before insert')
  402         );
  403         $dataOptions->addProperty($leaf);
  404 
  405         // begin SQL Statements
  406         $subgroup = new OptionsPropertySubgroup();
  407         $leaf = new MessageOnlyPropertyItem(
  408             __('Instead of <code>INSERT</code> statements, use:')
  409         );
  410         $subgroup->setSubgroupHeader($leaf);
  411 
  412         $leaf = new BoolPropertyItem(
  413             'delayed',
  414             __('<code>INSERT DELAYED</code> statements')
  415         );
  416         $leaf->setDoc(
  417             [
  418                 'manual_MySQL_Database_Administration',
  419                 'insert_delayed',
  420             ]
  421         );
  422         $subgroup->addProperty($leaf);
  423 
  424         $leaf = new BoolPropertyItem(
  425             'ignore',
  426             __('<code>INSERT IGNORE</code> statements')
  427         );
  428         $leaf->setDoc(
  429             [
  430                 'manual_MySQL_Database_Administration',
  431                 'insert',
  432             ]
  433         );
  434         $subgroup->addProperty($leaf);
  435         $dataOptions->addProperty($subgroup);
  436 
  437         // Function to use when dumping dat
  438         $leaf = new SelectPropertyItem(
  439             'type',
  440             __('Function to use when dumping data:')
  441         );
  442         $leaf->setValues(
  443             [
  444                 'INSERT'  => 'INSERT',
  445                 'UPDATE'  => 'UPDATE',
  446                 'REPLACE' => 'REPLACE',
  447             ]
  448         );
  449         $dataOptions->addProperty($leaf);
  450 
  451         /* Syntax to use when inserting data */
  452         $subgroup = new OptionsPropertySubgroup();
  453         $leaf = new MessageOnlyPropertyItem(
  454             null,
  455             __('Syntax to use when inserting data:')
  456         );
  457         $subgroup->setSubgroupHeader($leaf);
  458         $leaf = new RadioPropertyItem(
  459             'insert_syntax',
  460             __('<code>INSERT IGNORE</code> statements')
  461         );
  462         $leaf->setValues(
  463             [
  464                 'complete' => __(
  465                     'include column names in every <code>INSERT</code> statement'
  466                     . ' <br> &nbsp; &nbsp; &nbsp; Example: <code>INSERT INTO'
  467                     . ' tbl_name (col_A,col_B,col_C) VALUES (1,2,3)</code>'
  468                 ),
  469                 'extended' => __(
  470                     'insert multiple rows in every <code>INSERT</code> statement'
  471                     . '<br> &nbsp; &nbsp; &nbsp; Example: <code>INSERT INTO'
  472                     . ' tbl_name VALUES (1,2,3), (4,5,6), (7,8,9)</code>'
  473                 ),
  474                 'both'     => __(
  475                     'both of the above<br> &nbsp; &nbsp; &nbsp; Example:'
  476                     . ' <code>INSERT INTO tbl_name (col_A,col_B,col_C) VALUES'
  477                     . ' (1,2,3), (4,5,6), (7,8,9)</code>'
  478                 ),
  479                 'none'     => __(
  480                     'neither of the above<br> &nbsp; &nbsp; &nbsp; Example:'
  481                     . ' <code>INSERT INTO tbl_name VALUES (1,2,3)</code>'
  482                 ),
  483             ]
  484         );
  485         $subgroup->addProperty($leaf);
  486         $dataOptions->addProperty($subgroup);
  487 
  488         // Max length of query
  489         $leaf = new NumberPropertyItem(
  490             'max_query_size',
  491             __('Maximal length of created query')
  492         );
  493         $dataOptions->addProperty($leaf);
  494 
  495         // Dump binary columns in hexadecimal
  496         $leaf = new BoolPropertyItem(
  497             'hex_for_binary',
  498             __(
  499                 'Dump binary columns in hexadecimal notation'
  500                 . ' <i>(for example, "abc" becomes 0x616263)</i>'
  501             )
  502         );
  503         $dataOptions->addProperty($leaf);
  504 
  505         // Dump time in UTC
  506         $leaf = new BoolPropertyItem(
  507             'utc_time',
  508             __(
  509                 'Dump TIMESTAMP columns in UTC <i>(enables TIMESTAMP columns'
  510                 . ' to be dumped and reloaded between servers in different'
  511                 . ' time zones)</i>'
  512             )
  513         );
  514         $dataOptions->addProperty($leaf);
  515 
  516         // add the main group to the root group
  517         $exportSpecificOptions->addProperty($dataOptions);
  518 
  519         // set the options for the export plugin property item
  520         $exportPluginProperties->setOptions($exportSpecificOptions);
  521         $this->properties = $exportPluginProperties;
  522     }
  523 
  524     /**
  525      * Generates SQL for routines export
  526      *
  527      * @param string $db        Database
  528      * @param array  $aliases   Aliases of db/table/columns
  529      * @param string $type      Type of exported routine
  530      * @param string $name      Verbose name of exported routine
  531      * @param array  $routines  List of routines to export
  532      * @param string $delimiter Delimiter to use in SQL
  533      *
  534      * @return string SQL query
  535      */
  536     protected function exportRoutineSQL(
  537         $db,
  538         array $aliases,
  539         $type,
  540         $name,
  541         array $routines,
  542         $delimiter
  543     ) {
  544         global $crlf, $dbi;
  545 
  546         $text = $this->exportComment()
  547             . $this->exportComment($name)
  548             . $this->exportComment();
  549 
  550         $used_alias = false;
  551         $proc_query = '';
  552 
  553         foreach ($routines as $routine) {
  554             if (! empty($GLOBALS['sql_drop_table'])) {
  555                 $proc_query .= 'DROP ' . $type . ' IF EXISTS '
  556                     . Util::backquote($routine)
  557                     . $delimiter . $crlf;
  558             }
  559             $create_query = $this->replaceWithAliases(
  560                 $dbi->getDefinition($db, $type, $routine),
  561                 $aliases,
  562                 $db,
  563                 '',
  564                 $flag
  565             );
  566             // One warning per database
  567             if ($flag) {
  568                 $used_alias = true;
  569             }
  570             $proc_query .= $create_query . $delimiter . $crlf . $crlf;
  571         }
  572         if ($used_alias) {
  573             $text .= $this->exportComment(
  574                 __('It appears your database uses routines;')
  575             )
  576             . $this->exportComment(
  577                 __('alias export may not work reliably in all cases.')
  578             )
  579             . $this->exportComment();
  580         }
  581         $text .= $proc_query;
  582 
  583         return $text;
  584     }
  585 
  586     /**
  587      * Exports routines (procedures and functions)
  588      *
  589      * @param string $db      Database
  590      * @param array  $aliases Aliases of db/table/columns
  591      *
  592      * @return bool Whether it succeeded
  593      */
  594     public function exportRoutines($db, array $aliases = [])
  595     {
  596         global $crlf, $dbi;
  597 
  598         $db_alias = $db;
  599         $this->initAlias($aliases, $db_alias);
  600 
  601         $text = '';
  602         $delimiter = '$$';
  603 
  604         $procedure_names = $dbi
  605             ->getProceduresOrFunctions($db, 'PROCEDURE');
  606         $function_names = $dbi->getProceduresOrFunctions($db, 'FUNCTION');
  607 
  608         if ($procedure_names || $function_names) {
  609             $text .= $crlf
  610                 . 'DELIMITER ' . $delimiter . $crlf;
  611 
  612             if ($procedure_names) {
  613                 $text .= $this->exportRoutineSQL(
  614                     $db,
  615                     $aliases,
  616                     'PROCEDURE',
  617                     __('Procedures'),
  618                     $procedure_names,
  619                     $delimiter
  620                 );
  621             }
  622 
  623             if ($function_names) {
  624                 $text .= $this->exportRoutineSQL(
  625                     $db,
  626                     $aliases,
  627                     'FUNCTION',
  628                     __('Functions'),
  629                     $function_names,
  630                     $delimiter
  631                 );
  632             }
  633 
  634             $text .= 'DELIMITER ;' . $crlf;
  635         }
  636 
  637         if (! empty($text)) {
  638             return $this->export->outputHandler($text);
  639         }
  640 
  641         return false;
  642     }
  643 
  644     /**
  645      * Possibly outputs comment
  646      *
  647      * @param string $text Text of comment
  648      *
  649      * @return string The formatted comment
  650      */
  651     private function exportComment($text = '')
  652     {
  653         if (isset($GLOBALS['sql_include_comments'])
  654             && $GLOBALS['sql_include_comments']
  655         ) {
  656             // see https://dev.mysql.com/doc/refman/5.0/en/ansi-diff-comments.html
  657             if (empty($text)) {
  658                 return '--' . $GLOBALS['crlf'];
  659             }
  660 
  661             $lines = preg_split("/\\r\\n|\\r|\\n/", $text);
  662             $result = [];
  663             foreach ($lines as $line) {
  664                 $result[] = '-- ' . $line . $GLOBALS['crlf'];
  665             }
  666 
  667             return implode('', $result);
  668         }
  669 
  670         return '';
  671     }
  672 
  673     /**
  674      * Possibly outputs CRLF
  675      *
  676      * @return string crlf or nothing
  677      */
  678     private function possibleCRLF()
  679     {
  680         if (isset($GLOBALS['sql_include_comments'])
  681             && $GLOBALS['sql_include_comments']
  682         ) {
  683             return $GLOBALS['crlf'];
  684         }
  685 
  686         return '';
  687     }
  688 
  689     /**
  690      * Outputs export footer
  691      *
  692      * @return bool Whether it succeeded
  693      */
  694     public function exportFooter()
  695     {
  696         global $crlf, $dbi;
  697 
  698         $foot = '';
  699 
  700         if (isset($GLOBALS['sql_disable_fk'])) {
  701             $foot .= 'SET FOREIGN_KEY_CHECKS=1;' . $crlf;
  702         }
  703 
  704         if (isset($GLOBALS['sql_use_transaction'])) {
  705             $foot .= 'COMMIT;' . $crlf;
  706         }
  707 
  708         // restore connection settings
  709         if ($this->sentCharset) {
  710             $foot .= $crlf
  711                 . '/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;'
  712                 . $crlf
  713                 . '/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;'
  714                 . $crlf
  715                 . '/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;'
  716                 . $crlf;
  717             $this->sentCharset = false;
  718         }
  719 
  720         /* Restore timezone */
  721         if (isset($GLOBALS['sql_utc_time']) && $GLOBALS['sql_utc_time']) {
  722             $dbi->query('SET time_zone = "' . $GLOBALS['old_tz'] . '"');
  723         }
  724 
  725         return $this->export->outputHandler($foot);
  726     }
  727 
  728     /**
  729      * Outputs export header. It is the first method to be called, so all
  730      * the required variables are initialized here.
  731      *
  732      * @return bool Whether it succeeded
  733      */
  734     public function exportHeader()
  735     {
  736         global $crlf, $cfg, $dbi;
  737 
  738         if (isset($GLOBALS['sql_compatibility'])) {
  739             $tmp_compat = $GLOBALS['sql_compatibility'];
  740             if ($tmp_compat === 'NONE') {
  741                 $tmp_compat = '';
  742             }
  743             $dbi->tryQuery('SET SQL_MODE="' . $tmp_compat . '"');
  744             unset($tmp_compat);
  745         }
  746         $head = $this->exportComment('phpMyAdmin SQL Dump')
  747             . $this->exportComment('version ' . PMA_VERSION)
  748             . $this->exportComment('https://www.phpmyadmin.net/')
  749             . $this->exportComment();
  750         $host_string = __('Host:') . ' ' . $cfg['Server']['host'];
  751         if (! empty($cfg['Server']['port'])) {
  752             $host_string .= ':' . $cfg['Server']['port'];
  753         }
  754         $head .= $this->exportComment($host_string);
  755         $head .= $this->exportComment(
  756             __('Generation Time:') . ' '
  757             . Util::localisedDate()
  758         )
  759         . $this->exportComment(
  760             __('Server version:') . ' ' . $dbi->getVersionString()
  761         )
  762         . $this->exportComment(__('PHP Version:') . ' ' . PHP_VERSION)
  763         . $this->possibleCRLF();
  764 
  765         if (isset($GLOBALS['sql_header_comment'])
  766             && ! empty($GLOBALS['sql_header_comment'])
  767         ) {
  768             // '\n' is not a newline (like "\n" would be), it's the characters
  769             // backslash and n, as explained on the export interface
  770             $lines = explode('\n', $GLOBALS['sql_header_comment']);
  771             $head .= $this->exportComment();
  772             foreach ($lines as $one_line) {
  773                 $head .= $this->exportComment($one_line);
  774             }
  775             $head .= $this->exportComment();
  776         }
  777 
  778         if (isset($GLOBALS['sql_disable_fk'])) {
  779             $head .= 'SET FOREIGN_KEY_CHECKS=0;' . $crlf;
  780         }
  781 
  782         // We want exported AUTO_INCREMENT columns to have still same value,
  783         // do this only for recent MySQL exports
  784         if (! isset($GLOBALS['sql_compatibility'])
  785             || $GLOBALS['sql_compatibility'] === 'NONE'
  786         ) {
  787             $head .= 'SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";' . $crlf;
  788         }
  789 
  790         if (isset($GLOBALS['sql_use_transaction'])) {
  791             $head .= 'START TRANSACTION;' . $crlf;
  792         }
  793 
  794         /* Change timezone if we should export timestamps in UTC */
  795         if (isset($GLOBALS['sql_utc_time']) && $GLOBALS['sql_utc_time']) {
  796             $head .= 'SET time_zone = "+00:00";' . $crlf;
  797             $GLOBALS['old_tz'] = $dbi
  798                 ->fetchValue('SELECT @@session.time_zone');
  799             $dbi->query('SET time_zone = "+00:00"');
  800         }
  801 
  802         $head .= $this->possibleCRLF();
  803 
  804         if (! empty($GLOBALS['asfile'])) {
  805             // we are saving as file, therefore we provide charset information
  806             // so that a utility like the mysql client can interpret
  807             // the file correctly
  808             if (isset($GLOBALS['charset'], Charsets::$mysqlCharsetMap[$GLOBALS['charset']])) {
  809                 // we got a charset from the export dialog
  810                 $set_names = Charsets::$mysqlCharsetMap[$GLOBALS['charset']];
  811             } else {
  812                 // by default we use the connection charset
  813                 $set_names = Charsets::$mysqlCharsetMap['utf-8'];
  814             }
  815             if ($set_names === 'utf8' && $dbi->getVersion() > 50503) {
  816                 $set_names = 'utf8mb4';
  817             }
  818             $head .= $crlf
  819                 . '/*!40101 SET @OLD_CHARACTER_SET_CLIENT='
  820                 . '@@CHARACTER_SET_CLIENT */;' . $crlf
  821                 . '/*!40101 SET @OLD_CHARACTER_SET_RESULTS='
  822                 . '@@CHARACTER_SET_RESULTS */;' . $crlf
  823                 . '/*!40101 SET @OLD_COLLATION_CONNECTION='
  824                 . '@@COLLATION_CONNECTION */;' . $crlf
  825                 . '/*!40101 SET NAMES ' . $set_names . ' */;' . $crlf . $crlf;
  826             $this->sentCharset = true;
  827         }
  828 
  829         return $this->export->outputHandler($head);
  830     }
  831 
  832     /**
  833      * Outputs CREATE DATABASE statement
  834      *
  835      * @param string $db          Database name
  836      * @param string $export_type 'server', 'database', 'table'
  837      * @param string $db_alias    Aliases of db
  838      *
  839      * @return bool Whether it succeeded
  840      */
  841     public function exportDBCreate($db, $export_type, $db_alias = '')
  842     {
  843         global $crlf, $dbi;
  844 
  845         if (empty($db_alias)) {
  846             $db_alias = $db;
  847         }
  848         if (isset($GLOBALS['sql_compatibility'])) {
  849             $compat = $GLOBALS['sql_compatibility'];
  850         } else {
  851             $compat = 'NONE';
  852         }
  853         if (isset($GLOBALS['sql_drop_database'])) {
  854             if (! $this->export->outputHandler(
  855                 'DROP DATABASE IF EXISTS '
  856                 . Util::backquoteCompat(
  857                     $db_alias,
  858                     $compat,
  859                     isset($GLOBALS['sql_backquotes'])
  860                 )
  861                 . ';' . $crlf
  862             )
  863             ) {
  864                 return false;
  865             }
  866         }
  867         if ($export_type === 'database' && ! isset($GLOBALS['sql_create_database'])) {
  868             return true;
  869         }
  870 
  871         $create_query = 'CREATE DATABASE IF NOT EXISTS '
  872             . Util::backquoteCompat(
  873                 $db_alias,
  874                 $compat,
  875                 isset($GLOBALS['sql_backquotes'])
  876             );
  877         $collation = $dbi->getDbCollation($db);
  878         if (mb_strpos($collation, '_')) {
  879             $create_query .= ' DEFAULT CHARACTER SET '
  880                 . mb_substr(
  881                     $collation,
  882                     0,
  883                     (int) mb_strpos($collation, '_')
  884                 )
  885                 . ' COLLATE ' . $collation;
  886         } else {
  887             $create_query .= ' DEFAULT CHARACTER SET ' . $collation;
  888         }
  889         $create_query .= ';' . $crlf;
  890         if (! $this->export->outputHandler($create_query)) {
  891             return false;
  892         }
  893 
  894         return $this->exportUseStatement($db_alias, $compat);
  895     }
  896 
  897     /**
  898      * Outputs USE statement
  899      *
  900      * @param string $db     db to use
  901      * @param string $compat sql compatibility
  902      *
  903      * @return bool Whether it succeeded
  904      */
  905     private function exportUseStatement($db, $compat)
  906     {
  907         global $crlf;
  908 
  909         if (isset($GLOBALS['sql_compatibility'])
  910             && $GLOBALS['sql_compatibility'] === 'NONE'
  911         ) {
  912             $result = $this->export->outputHandler(
  913                 'USE '
  914                 . Util::backquoteCompat(
  915                     $db,
  916                     $compat,
  917                     isset($GLOBALS['sql_backquotes'])
  918                 )
  919                 . ';' . $crlf
  920             );
  921         } else {
  922             $result = $this->export->outputHandler('USE ' . $db . ';' . $crlf);
  923         }
  924 
  925         return $result;
  926     }
  927 
  928     /**
  929      * Outputs database header
  930      *
  931      * @param string $db       Database name
  932      * @param string $db_alias Alias of db
  933      *
  934      * @return bool Whether it succeeded
  935      */
  936     public function exportDBHeader($db, $db_alias = '')
  937     {
  938         if (empty($db_alias)) {
  939             $db_alias = $db;
  940         }
  941         if (isset($GLOBALS['sql_compatibility'])) {
  942             $compat = $GLOBALS['sql_compatibility'];
  943         } else {
  944             $compat = 'NONE';
  945         }
  946         $head = $this->exportComment()
  947             . $this->exportComment(
  948                 __('Database:') . ' '
  949                 . Util::backquoteCompat(
  950                     $db_alias,
  951                     $compat,
  952                     isset($GLOBALS['sql_backquotes'])
  953                 )
  954             )
  955             . $this->exportComment();
  956 
  957         return $this->export->outputHandler($head);
  958     }
  959 
  960     /**
  961      * Outputs database footer
  962      *
  963      * @param string $db Database name
  964      *
  965      * @return bool Whether it succeeded
  966      */
  967     public function exportDBFooter($db)
  968     {
  969         global $crlf;
  970 
  971         $result = true;
  972 
  973         //add indexes to the sql dump file
  974         if (isset($GLOBALS['sql_indexes'])) {
  975             $result = $this->export->outputHandler($GLOBALS['sql_indexes']);
  976             unset($GLOBALS['sql_indexes']);
  977         }
  978         //add auto increments to the sql dump file
  979         if (isset($GLOBALS['sql_auto_increments'])) {
  980             $result = $this->export->outputHandler($GLOBALS['sql_auto_increments']);
  981             unset($GLOBALS['sql_auto_increments']);
  982         }
  983         //add constraints to the sql dump file
  984         if (isset($GLOBALS['sql_constraints'])) {
  985             $result = $this->export->outputHandler($GLOBALS['sql_constraints']);
  986             unset($GLOBALS['sql_constraints']);
  987         }
  988 
  989         return $result;
  990     }
  991 
  992     /**
  993      * Exports events
  994      *
  995      * @param string $db Database
  996      *
  997      * @return bool Whether it succeeded
  998      */
  999     public function exportEvents($db)
 1000     {
 1001         global $crlf, $dbi;
 1002 
 1003         $text = '';
 1004         $delimiter = '$$';
 1005 
 1006         $event_names = $dbi->fetchResult(
 1007             'SELECT EVENT_NAME FROM information_schema.EVENTS WHERE'
 1008             . " EVENT_SCHEMA= '" . $dbi->escapeString($db)
 1009             . "';"
 1010         );
 1011 
 1012         if ($event_names) {
 1013             $text .= $crlf
 1014                 . 'DELIMITER ' . $delimiter . $crlf;
 1015 
 1016             $text .= $this->exportComment()
 1017                 . $this->exportComment(__('Events'))
 1018                 . $this->exportComment();
 1019 
 1020             foreach ($event_names as $event_name) {
 1021                 if (! empty($GLOBALS['sql_drop_table'])) {
 1022                     $text .= 'DROP EVENT IF EXISTS '
 1023                         . Util::backquote($event_name)
 1024                         . $delimiter . $crlf;
 1025                 }
 1026                 $text .= $dbi->getDefinition($db, 'EVENT', $event_name)
 1027                     . $delimiter . $crlf . $crlf;
 1028             }
 1029 
 1030             $text .= 'DELIMITER ;' . $crlf;
 1031         }
 1032 
 1033         if (! empty($text)) {
 1034             return $this->export->outputHandler($text);
 1035         }
 1036 
 1037         return false;
 1038     }
 1039 
 1040     /**
 1041      * Exports metadata from Configuration Storage
 1042      *
 1043      * @param string       $db            database being exported
 1044      * @param string|array $tables        table(s) being exported
 1045      * @param array        $metadataTypes types of metadata to export
 1046      *
 1047      * @return bool Whether it succeeded
 1048      */
 1049     public function exportMetadata(
 1050         $db,
 1051         $tables,
 1052         array $metadataTypes
 1053     ) {
 1054         $cfgRelation = $this->relation->getRelationsParam();
 1055         if (! isset($cfgRelation['db'])) {
 1056             return true;
 1057         }
 1058 
 1059         $comment = $this->possibleCRLF()
 1060             . $this->possibleCRLF()
 1061             . $this->exportComment()
 1062             . $this->exportComment(__('Metadata'))
 1063             . $this->exportComment();
 1064         if (! $this->export->outputHandler($comment)) {
 1065             return false;
 1066         }
 1067 
 1068         if (! $this->exportUseStatement(
 1069             $cfgRelation['db'],
 1070             $GLOBALS['sql_compatibility']
 1071         )
 1072         ) {
 1073             return false;
 1074         }
 1075 
 1076         $r = true;
 1077         if (is_array($tables)) {
 1078             // export metadata for each table
 1079             foreach ($tables as $table) {
 1080                 $r &= $this->exportConfigurationMetadata($db, $table, $metadataTypes);
 1081             }
 1082             // export metadata for the database
 1083             $r &= $this->exportConfigurationMetadata($db, null, $metadataTypes);
 1084         } else {
 1085             // export metadata for single table
 1086             $r &= $this->exportConfigurationMetadata($db, $tables, $metadataTypes);
 1087         }
 1088 
 1089         return (bool) $r;
 1090     }
 1091 
 1092     /**
 1093      * Exports metadata from Configuration Storage
 1094      *
 1095      * @param string      $db            database being exported
 1096      * @param string|null $table         table being exported
 1097      * @param array       $metadataTypes types of metadata to export
 1098      *
 1099      * @return bool Whether it succeeded
 1100      */
 1101     private function exportConfigurationMetadata(
 1102         $db,
 1103         $table,
 1104         array $metadataTypes
 1105     ) {
 1106         global $dbi;
 1107 
 1108         $cfgRelation = $this->relation->getRelationsParam();
 1109 
 1110         if (isset($table)) {
 1111             $types = [
 1112                 'column_info'   => 'db_name',
 1113                 'table_uiprefs' => 'db_name',
 1114                 'tracking'      => 'db_name',
 1115             ];
 1116         } else {
 1117             $types = [
 1118                 'bookmark'        => 'dbase',
 1119                 'relation'        => 'master_db',
 1120                 'pdf_pages'       => 'db_name',
 1121                 'savedsearches'   => 'db_name',
 1122                 'central_columns' => 'db_name',
 1123             ];
 1124         }
 1125 
 1126         $aliases = [];
 1127 
 1128         $comment = $this->possibleCRLF()
 1129             . $this->exportComment();
 1130 
 1131         if (isset($table)) {
 1132             $comment .= $this->exportComment(
 1133                 sprintf(
 1134                     __('Metadata for table %s'),
 1135                     $table
 1136                 )
 1137             );
 1138         } else {
 1139             $comment .= $this->exportComment(
 1140                 sprintf(
 1141                     __('Metadata for database %s'),
 1142                     $db
 1143                 )
 1144             );
 1145         }
 1146 
 1147         $comment .= $this->exportComment();
 1148 
 1149         if (! $this->export->outputHandler($comment)) {
 1150             return false;
 1151         }
 1152 
 1153         foreach ($types as $type => $dbNameColumn) {
 1154             if (! in_array($type, $metadataTypes) || ! isset($cfgRelation[$type])) {
 1155                 continue;
 1156             }
 1157 
 1158             // special case, designer pages and their coordinates
 1159             if ($type === 'pdf_pages') {
 1160                 $sql_query = 'SELECT `page_nr`, `page_descr` FROM '
 1161                     . Util::backquote($cfgRelation['db'])
 1162                     . '.' . Util::backquote($cfgRelation[$type])
 1163                     . ' WHERE ' . Util::backquote($dbNameColumn)
 1164                     . " = '" . $dbi->escapeString($db) . "'";
 1165 
 1166                 $result = $dbi->fetchResult(
 1167                     $sql_query,
 1168                     'page_nr',
 1169                     'page_descr'
 1170                 );
 1171 
 1172                 foreach ($result as $page => $name) {
 1173                     // insert row for pdf_page
 1174                     $sql_query_row = 'SELECT `db_name`, `page_descr` FROM '
 1175                         . Util::backquote($cfgRelation['db'])
 1176                         . '.' . Util::backquote(
 1177                             $cfgRelation[$type]
 1178                         )
 1179                         . ' WHERE ' . Util::backquote(
 1180                             $dbNameColumn
 1181                         )
 1182                         . " = '" . $dbi->escapeString($db) . "'"
 1183                         . " AND `page_nr` = '" . intval($page) . "'";
 1184 
 1185                     if (! $this->exportData(
 1186                         $cfgRelation['db'],
 1187                         $cfgRelation[$type],
 1188                         $GLOBALS['crlf'],
 1189                         '',
 1190                         $sql_query_row,
 1191                         $aliases
 1192                     )
 1193                     ) {
 1194                         return false;
 1195                     }
 1196 
 1197                     $lastPage = $GLOBALS['crlf']
 1198                         . 'SET @LAST_PAGE = LAST_INSERT_ID();'
 1199                         . $GLOBALS['crlf'];
 1200                     if (! $this->export->outputHandler($lastPage)) {
 1201                         return false;
 1202                     }
 1203 
 1204                     $sql_query_coords = 'SELECT `db_name`, `table_name`, '
 1205                         . "'@LAST_PAGE' AS `pdf_page_number`, `x`, `y` FROM "
 1206                         . Util::backquote($cfgRelation['db'])
 1207                         . '.' . Util::backquote(
 1208                             $cfgRelation['table_coords']
 1209                         )
 1210                         . " WHERE `pdf_page_number` = '" . $page . "'";
 1211 
 1212                     $GLOBALS['exporting_metadata'] = true;
 1213                     if (! $this->exportData(
 1214                         $cfgRelation['db'],
 1215                         $cfgRelation['table_coords'],
 1216                         $GLOBALS['crlf'],
 1217                         '',
 1218                         $sql_query_coords,
 1219                         $aliases
 1220                     )
 1221                     ) {
 1222                         $GLOBALS['exporting_metadata'] = false;
 1223 
 1224                         return false;
 1225                     }
 1226                     $GLOBALS['exporting_metadata'] = false;
 1227                 }
 1228                 continue;
 1229             }
 1230 
 1231             // remove auto_incrementing id field for some tables
 1232             if ($type === 'bookmark') {
 1233                 $sql_query = 'SELECT `dbase`, `user`, `label`, `query` FROM ';
 1234             } elseif ($type === 'column_info') {
 1235                 $sql_query = 'SELECT `db_name`, `table_name`, `column_name`,'
 1236                     . ' `comment`, `mimetype`, `transformation`,'
 1237                     . ' `transformation_options`, `input_transformation`,'
 1238                     . ' `input_transformation_options` FROM';
 1239             } elseif ($type === 'savedsearches') {
 1240                 $sql_query = 'SELECT `username`, `db_name`, `search_name`,'
 1241                     . ' `search_data` FROM';
 1242             } else {
 1243                 $sql_query = 'SELECT * FROM ';
 1244             }
 1245             $sql_query .= Util::backquote($cfgRelation['db'])
 1246                 . '.' . Util::backquote($cfgRelation[$type])
 1247                 . ' WHERE ' . Util::backquote($dbNameColumn)
 1248                 . " = '" . $dbi->escapeString($db) . "'";
 1249             if (isset($table)) {
 1250                 $sql_query .= " AND `table_name` = '"
 1251                     . $dbi->escapeString($table) . "'";
 1252             }
 1253 
 1254             if (! $this->exportData(
 1255                 $cfgRelation['db'],
 1256                 $cfgRelation[$type],
 1257                 $GLOBALS['crlf'],
 1258                 '',
 1259                 $sql_query,
 1260                 $aliases
 1261             )
 1262             ) {
 1263                 return false;
 1264             }
 1265         }
 1266 
 1267         return true;
 1268     }
 1269 
 1270     /**
 1271      * Returns a stand-in CREATE definition to resolve view dependencies
 1272      *
 1273      * @param string $db      the database name
 1274      * @param string $view    the view name
 1275      * @param string $crlf    the end of line sequence
 1276      * @param array  $aliases Aliases of db/table/columns
 1277      *
 1278      * @return string resulting definition
 1279      */
 1280     public function getTableDefStandIn($db, $view, $crlf, $aliases = [])
 1281     {
 1282         global $dbi;
 1283 
 1284         $db_alias = $db;
 1285         $view_alias = $view;
 1286         $this->initAlias($aliases, $db_alias, $view_alias);
 1287         $create_query = '';
 1288         if (! empty($GLOBALS['sql_drop_table'])) {
 1289             $create_query .= 'DROP VIEW IF EXISTS '
 1290                 . Util::backquote($view_alias)
 1291                 . ';' . $crlf;
 1292         }
 1293 
 1294         $create_query .= 'CREATE TABLE ';
 1295 
 1296         if (isset($GLOBALS['sql_if_not_exists'])
 1297             && $GLOBALS['sql_if_not_exists']
 1298         ) {
 1299             $create_query .= 'IF NOT EXISTS ';
 1300         }
 1301         $create_query .= Util::backquote($view_alias) . ' (' . $crlf;
 1302         $tmp = [];
 1303         $columns = $dbi->getColumnsFull($db, $view);
 1304         foreach ($columns as $column_name => $definition) {
 1305             $col_alias = $column_name;
 1306             if (! empty($aliases[$db]['tables'][$view]['columns'][$col_alias])) {
 1307                 $col_alias = $aliases[$db]['tables'][$view]['columns'][$col_alias];
 1308             }
 1309             $tmp[] = Util::backquote($col_alias) . ' ' .
 1310                 $definition['Type'] . $crlf;
 1311         }
 1312 
 1313         return $create_query . implode(',', $tmp) . ');' . $crlf;
 1314     }
 1315 
 1316     /**
 1317      * Returns CREATE definition that matches $view's structure
 1318      *
 1319      * @param string $db            the database name
 1320      * @param string $view          the view name
 1321      * @param string $crlf          the end of line sequence
 1322      * @param bool   $add_semicolon whether to add semicolon and end-of-line at
 1323      *                              the end
 1324      * @param array  $aliases       Aliases of db/table/columns
 1325      *
 1326      * @return string resulting schema
 1327      */
 1328     private function getTableDefForView(
 1329         $db,
 1330         $view,
 1331         $crlf,
 1332         $add_semicolon = true,
 1333         array $aliases = []
 1334     ) {
 1335         global $dbi;
 1336 
 1337         $db_alias = $db;
 1338         $view_alias = $view;
 1339         $this->initAlias($aliases, $db_alias, $view_alias);
 1340         $create_query = 'CREATE TABLE';
 1341         if (isset($GLOBALS['sql_if_not_exists'])) {
 1342             $create_query .= ' IF NOT EXISTS ';
 1343         }
 1344         $create_query .= Util::backquote($view_alias) . '(' . $crlf;
 1345 
 1346         $columns = $dbi->getColumns($db, $view, null, true);
 1347 
 1348         $firstCol = true;
 1349         foreach ($columns as $column) {
 1350             $col_alias = $column['Field'];
 1351             if (! empty($aliases[$db]['tables'][$view]['columns'][$col_alias])) {
 1352                 $col_alias = $aliases[$db]['tables'][$view]['columns'][$col_alias];
 1353             }
 1354             $extracted_columnspec = Util::extractColumnSpec(
 1355                 $column['Type']
 1356             );
 1357 
 1358             if (! $firstCol) {
 1359                 $create_query .= ',' . $crlf;
 1360             }
 1361             $create_query .= '    ' . Util::backquote($col_alias);
 1362             $create_query .= ' ' . $column['Type'];
 1363             if ($extracted_columnspec['can_contain_collation']
 1364                 && ! empty($column['Collation'])
 1365             ) {
 1366                 $create_query .= ' COLLATE ' . $column['Collation'];
 1367             }
 1368             if ($column['Null'] === 'NO') {
 1369                 $create_query .= ' NOT NULL';
 1370             }
 1371             if (isset($column['Default'])) {
 1372                 $create_query .= " DEFAULT '"
 1373                     . $dbi->escapeString($column['Default']) . "'";
 1374             } else {
 1375                 if ($column['Null'] === 'YES') {
 1376                     $create_query .= ' DEFAULT NULL';
 1377                 }
 1378             }
 1379             if (! empty($column['Comment'])) {
 1380                 $create_query .= " COMMENT '"
 1381                     . $dbi->escapeString($column['Comment']) . "'";
 1382             }
 1383             $firstCol = false;
 1384         }
 1385         $create_query .= $crlf . ')' . ($add_semicolon ? ';' : '') . $crlf;
 1386 
 1387         if (isset($GLOBALS['sql_compatibility'])) {
 1388             $compat = $GLOBALS['sql_compatibility'];
 1389         } else {
 1390             $compat = 'NONE';
 1391         }
 1392         if ($compat === 'MSSQL') {
 1393             $create_query = $this->makeCreateTableMSSQLCompatible(
 1394                 $create_query
 1395             );
 1396         }
 1397 
 1398         return $create_query;
 1399     }
 1400 
 1401     /**
 1402      * Returns $table's CREATE definition
 1403      *
 1404      * @param string $db                        the database name
 1405      * @param string $table                     the table name
 1406      * @param string $crlf                      the end of line sequence
 1407      * @param string $error_url                 the url to go back in case
 1408      *                                          of error
 1409      * @param bool   $show_dates                whether to include creation/
 1410      *                                          update/check dates
 1411      * @param bool   $add_semicolon             whether to add semicolon and
 1412      *                                          end-of-line at the end
 1413      * @param bool   $view                      whether we're handling a view
 1414      * @param bool   $update_indexes_increments whether we need to update
 1415      *                                          two global variables
 1416      * @param array  $aliases                   Aliases of db/table/columns
 1417      *
 1418      * @return string resulting schema
 1419      */
 1420     public function getTableDef(
 1421         $db,
 1422         $table,
 1423         $crlf,
 1424         $error_url,
 1425         $show_dates = false,
 1426         $add_semicolon = true,
 1427         $view = false,
 1428         $update_indexes_increments = true,
 1429         array $aliases = []
 1430     ) {
 1431         global $sql_drop_table, $sql_backquotes, $sql_constraints,
 1432                $sql_constraints_query, $sql_indexes, $sql_indexes_query,
 1433                $sql_auto_increments, $sql_drop_foreign_keys, $dbi;
 1434 
 1435         $db_alias = $db;
 1436         $table_alias = $table;
 1437         $this->initAlias($aliases, $db_alias, $table_alias);
 1438 
 1439         $schema_create = '';
 1440         $auto_increment = '';
 1441         $new_crlf = $crlf;
 1442 
 1443         if (isset($GLOBALS['sql_compatibility'])) {
 1444             $compat = $GLOBALS['sql_compatibility'];
 1445         } else {
 1446             $compat = 'NONE';
 1447         }
 1448 
 1449         // need to use PhpMyAdmin\DatabaseInterface::QUERY_STORE
 1450         // with $dbi->numRows() in mysqli
 1451         $result = $dbi->tryQuery(
 1452             'SHOW TABLE STATUS FROM ' . Util::backquote($db)
 1453             . ' WHERE Name = \'' . $dbi->escapeString((string) $table) . '\'',
 1454             DatabaseInterface::CONNECT_USER,
 1455             DatabaseInterface::QUERY_STORE
 1456         );
 1457         if ($result != false) {
 1458             if ($dbi->numRows($result) > 0) {
 1459                 $tmpres = $dbi->fetchAssoc($result);
 1460 
 1461                 // Here we optionally add the AUTO_INCREMENT next value,
 1462                 // but starting with MySQL 5.0.24, the clause is already included
 1463                 // in SHOW CREATE TABLE so we'll remove it below
 1464                 if (isset($GLOBALS['sql_auto_increment'])
 1465                     && ! empty($tmpres['Auto_increment'])
 1466                 ) {
 1467                     $auto_increment .= ' AUTO_INCREMENT='
 1468                         . $tmpres['Auto_increment'] . ' ';
 1469                 }
 1470 
 1471                 if ($show_dates
 1472                     && isset($tmpres['Create_time'])
 1473                     && ! empty($tmpres['Create_time'])
 1474                 ) {
 1475                     $schema_create .= $this->exportComment(
 1476                         __('Creation:') . ' '
 1477                         . Util::localisedDate(
 1478                             strtotime($tmpres['Create_time'])
 1479                         )
 1480                     );
 1481                     $new_crlf = $this->exportComment() . $crlf;
 1482                 }
 1483 
 1484                 if ($show_dates
 1485                     && isset($tmpres['Update_time'])
 1486                     && ! empty($tmpres['Update_time'])
 1487                 ) {
 1488                     $schema_create .= $this->exportComment(
 1489                         __('Last update:') . ' '
 1490                         . Util::localisedDate(
 1491                             strtotime($tmpres['Update_time'])
 1492                         )
 1493                     );
 1494                     $new_crlf = $this->exportComment() . $crlf;
 1495                 }
 1496 
 1497                 if ($show_dates
 1498                     && isset($tmpres['Check_time'])
 1499                     && ! empty($tmpres['Check_time'])
 1500                 ) {
 1501                     $schema_create .= $this->exportComment(
 1502                         __('Last check:') . ' '
 1503                         . Util::localisedDate(
 1504                             strtotime($tmpres['Check_time'])
 1505                         )
 1506                     );
 1507                     $new_crlf = $this->exportComment() . $crlf;
 1508                 }
 1509             }
 1510             $dbi->freeResult($result);
 1511         }
 1512 
 1513         $schema_create .= $new_crlf;
 1514 
 1515         if (! empty($sql_drop_table)
 1516             && $dbi->getTable($db, $table)->isView()
 1517         ) {
 1518             $schema_create .= 'DROP VIEW IF EXISTS '
 1519                 . Util::backquote($table_alias, $sql_backquotes) . ';'
 1520                 . $crlf;
 1521         }
 1522 
 1523         // no need to generate a DROP VIEW here, it was done earlier
 1524         if (! empty($sql_drop_table)
 1525             && ! $dbi->getTable($db, $table)->isView()
 1526         ) {
 1527             $schema_create .= 'DROP TABLE IF EXISTS '
 1528                 . Util::backquote($table_alias, $sql_backquotes) . ';'
 1529                 . $crlf;
 1530         }
 1531 
 1532         // Complete table dump,
 1533         // Whether to quote table and column names or not
 1534         if ($sql_backquotes) {
 1535             $dbi->query('SET SQL_QUOTE_SHOW_CREATE = 1');
 1536         } else {
 1537             $dbi->query('SET SQL_QUOTE_SHOW_CREATE = 0');
 1538         }
 1539 
 1540         // I don't see the reason why this unbuffered query could cause problems,
 1541         // because SHOW CREATE TABLE returns only one row, and we free the
 1542         // results below. Nonetheless, we got 2 user reports about this
 1543         // (see bug 1562533) so I removed the unbuffered mode.
 1544         // $result = $dbi->query('SHOW CREATE TABLE ' . backquote($db)
 1545         // . '.' . backquote($table), null, DatabaseInterface::QUERY_UNBUFFERED);
 1546         //
 1547         // Note: SHOW CREATE TABLE, at least in MySQL 5.1.23, does not
 1548         // produce a displayable result for the default value of a BIT
 1549         // column, nor does the mysqldump command. See MySQL bug 35796
 1550         $dbi->tryQuery('USE ' . Util::backquote($db));
 1551         $result = $dbi->tryQuery(
 1552             'SHOW CREATE TABLE ' . Util::backquote($db) . '.'
 1553             . Util::backquote($table)
 1554         );
 1555         // an error can happen, for example the table is crashed
 1556         $tmp_error = $dbi->getError();
 1557         if ($tmp_error) {
 1558             $message = sprintf(__('Error reading structure for table %s:'), $db . '.' . $table);
 1559             $message .= ' ' . $tmp_error;
 1560             if (! defined('TESTSUITE')) {
 1561                 trigger_error($message, E_USER_ERROR);
 1562             }
 1563 
 1564             return $this->exportComment($message);
 1565         }
 1566 
 1567         // Old mode is stored so it can be restored once exporting is done.
 1568         $old_mode = Context::$MODE;
 1569 
 1570         $warning = '';
 1571 
 1572         $row = null;
 1573         if ($result !== false) {
 1574             $row = $dbi->fetchRow($result);
 1575         }
 1576 
 1577         if ($row) {
 1578             $create_query = $row[1];
 1579             unset($row);
 1580 
 1581             // Convert end of line chars to one that we want (note that MySQL
 1582             // doesn't return query it will accept in all cases)
 1583             if (mb_strpos($create_query, "(\r\n ")) {
 1584                 $create_query = str_replace("\r\n", $crlf, $create_query);
 1585             } elseif (mb_strpos($create_query, "(\n ")) {
 1586                 $create_query = str_replace("\n", $crlf, $create_query);
 1587             } elseif (mb_strpos($create_query, "(\r ")) {
 1588                 $create_query = str_replace("\r", $crlf, $create_query);
 1589             }
 1590 
 1591             /*
 1592              * Drop database name from VIEW creation.
 1593              *
 1594              * This is a bit tricky, but we need to issue SHOW CREATE TABLE with
 1595              * database name, but we don't want name to show up in CREATE VIEW
 1596              * statement.
 1597              */
 1598             if ($view) {
 1599                 //TODO: use parser
 1600                 $create_query = preg_replace(
 1601                     '/' . preg_quote(Util::backquote($db), '/') . '\./',
 1602                     '',
 1603                     $create_query
 1604                 );
 1605                 $parser = new Parser($create_query);
 1606                 /**
 1607                  * `CREATE TABLE` statement.
 1608                  *
 1609                  * @var CreateStatement
 1610                  */
 1611                 $statement = $parser->statements[0];
 1612 
 1613                 // exclude definition of current user
 1614                 if (isset($GLOBALS['sql_view_current_user'])) {
 1615                     $statement->options->remove('DEFINER');
 1616                 }
 1617 
 1618                 if (isset($GLOBALS['sql_simple_view_export'])) {
 1619                     $statement->options->remove('SQL SECURITY');
 1620                     $statement->options->remove('INVOKER');
 1621                     $statement->options->remove('ALGORITHM');
 1622                     $statement->options->remove('DEFINER');
 1623                 }
 1624                 $create_query = $statement->build();
 1625 
 1626                 // whether to replace existing view or not
 1627                 if (isset($GLOBALS['sql_or_replace_view'])) {
 1628                     $create_query = preg_replace(
 1629                         '/^CREATE/',
 1630                         'CREATE OR REPLACE',
 1631                         $create_query
 1632                     );
 1633                 }
 1634             }
 1635 
 1636             // Substitute aliases in `CREATE` query.
 1637             $create_query = $this->replaceWithAliases(
 1638                 $create_query,
 1639                 $aliases,
 1640                 $db,
 1641                 $table,
 1642                 $flag
 1643             );
 1644 
 1645             // One warning per view.
 1646             if ($flag && $view) {
 1647                 $warning = $this->exportComment()
 1648                     . $this->exportComment(
 1649                         __('It appears your database uses views;')
 1650                     )
 1651                     . $this->exportComment(
 1652                         __('alias export may not work reliably in all cases.')
 1653                     )
 1654                     . $this->exportComment();
 1655             }
 1656 
 1657             // Adding IF NOT EXISTS, if required.
 1658             if (isset($GLOBALS['sql_if_not_exists'])) {
 1659                 $create_query = preg_replace(
 1660                     '/^CREATE TABLE/',
 1661                     'CREATE TABLE IF NOT EXISTS',
 1662                     $create_query
 1663                 );
 1664             }
 1665 
 1666             // Making the query MSSQL compatible.
 1667             if ($compat === 'MSSQL') {
 1668                 $create_query = $this->makeCreateTableMSSQLCompatible(
 1669                     $create_query
 1670                 );
 1671             }
 1672 
 1673             // Views have no constraints, indexes, etc. They do not require any
 1674             // analysis.
 1675             if (! $view) {
 1676                 if (empty($sql_backquotes)) {
 1677                     // Option "Enclose table and column names with backquotes"
 1678                     // was checked.
 1679                     Context::$MODE |= Context::SQL_MODE_NO_ENCLOSING_QUOTES;
 1680                 }
 1681 
 1682                 // Using appropriate quotes.
 1683                 if (($compat === 'MSSQL') || ($sql_backquotes === '"')) {
 1684                     Context::$MODE |= Context::SQL_MODE_ANSI_QUOTES;
 1685                 }
 1686             }
 1687 
 1688             /**
 1689              * Parser used for analysis.
 1690              *
 1691              * @var Parser
 1692              */
 1693             $parser = new Parser($create_query);
 1694 
 1695             /**
 1696              * `CREATE TABLE` statement.
 1697              *
 1698              * @var CreateStatement
 1699              */
 1700             $statement = $parser->statements[0];
 1701 
 1702             if (! empty($statement->entityOptions)) {
 1703                 $engine = $statement->entityOptions->has('ENGINE');
 1704             } else {
 1705                 $engine = '';
 1706             }
 1707 
 1708             /* Avoid operation on ARCHIVE tables as those can not be altered */
 1709             if (! empty($statement->fields) && (empty($engine) || strtoupper($engine) !== 'ARCHIVE')) {
 1710 
 1711                 /**
 1712                  * Fragments containing definition of each constraint.
 1713                  *
 1714                  * @var array
 1715                  */
 1716                 $constraints = [];
 1717 
 1718                 /**
 1719                  * Fragments containing definition of each index.
 1720                  *
 1721                  * @var array
 1722                  */
 1723                 $indexes = [];
 1724 
 1725                 /**
 1726                  * Fragments containing definition of each FULLTEXT index.
 1727                  *
 1728                  * @var array
 1729                  */
 1730                 $indexes_fulltext = [];
 1731 
 1732                 /**
 1733                  * Fragments containing definition of each foreign key that will
 1734                  * be dropped.
 1735                  *
 1736                  * @var array
 1737                  */
 1738                 $dropped = [];
 1739 
 1740                 /**
 1741                  * Fragment containing definition of the `AUTO_INCREMENT`.
 1742                  *
 1743                  * @var array
 1744                  */
 1745                 $auto_increment = [];
 1746 
 1747                 // Scanning each field of the `CREATE` statement to fill the arrays
 1748                 // above.
 1749                 // If the field is used in any of the arrays above, it is removed
 1750                 // from the original definition.
 1751                 // Also, AUTO_INCREMENT attribute is removed.
 1752                 /** @var CreateDefinition $field */
 1753                 foreach ($statement->fields as $key => $field) {
 1754                     if ($field->isConstraint) {
 1755                         // Creating the parts that add constraints.
 1756                         $constraints[] = $field::build($field);
 1757                         unset($statement->fields[$key]);
 1758                     } elseif (! empty($field->key)) {
 1759                         // Creating the parts that add indexes (must not be
 1760                         // constraints).
 1761                         if ($field->key->type === 'FULLTEXT KEY') {
 1762                             $indexes_fulltext[] = $field::build($field);
 1763                             unset($statement->fields[$key]);
 1764                         } else {
 1765                             if (empty($GLOBALS['sql_if_not_exists'])) {
 1766                                 $indexes[] = str_replace(
 1767                                     'COMMENT=\'',
 1768                                     'COMMENT \'',
 1769                                     $field::build($field)
 1770                                 );
 1771                                 unset($statement->fields[$key]);
 1772                             }
 1773                         }
 1774                     }
 1775 
 1776                     // Creating the parts that drop foreign keys.
 1777                     if (! empty($field->key)) {
 1778                         if ($field->key->type === 'FOREIGN KEY') {
 1779                             $dropped[] = 'FOREIGN KEY ' . Context::escape(
 1780                                 $field->name
 1781                             );
 1782                             unset($statement->fields[$key]);
 1783                         }
 1784                     }
 1785 
 1786                     // Dropping AUTO_INCREMENT.
 1787                     if (empty($field->options)) {
 1788                         continue;
 1789                     }
 1790 
 1791                     if (! $field->options->has('AUTO_INCREMENT')
 1792                         || ! empty($GLOBALS['sql_if_not_exists'])
 1793                     ) {
 1794                         continue;
 1795                     }
 1796 
 1797                     $auto_increment[] = $field::build($field);
 1798                     $field->options->remove('AUTO_INCREMENT');
 1799                 }
 1800 
 1801                 /**
 1802                  * The header of the `ALTER` statement (`ALTER TABLE tbl`).
 1803                  *
 1804                  * @var string
 1805                  */
 1806                 $alter_header = 'ALTER TABLE ' .
 1807                     Util::backquoteCompat(
 1808                         $table_alias,
 1809                         $compat,
 1810                         $sql_backquotes
 1811                     );
 1812 
 1813                 /**
 1814                  * The footer of the `ALTER` statement (usually ';')
 1815                  *
 1816                  * @var string
 1817                  */
 1818                 $alter_footer = ';' . $crlf;
 1819 
 1820                 // Generating constraints-related query.
 1821                 if (! empty($constraints)) {
 1822                     $sql_constraints_query = $alter_header . $crlf . '  ADD '
 1823                         . implode(',' . $crlf . '  ADD ', $constraints)
 1824                         . $alter_footer;
 1825 
 1826                     $sql_constraints = $this->generateComment(
 1827                         $crlf,
 1828                         $sql_constraints,
 1829                         __('Constraints for dumped tables'),
 1830                         __('Constraints for table'),
 1831                         $table_alias,
 1832                         $compat
 1833                     ) . $sql_constraints_query;
 1834                 }
 1835 
 1836                 // Generating indexes-related query.
 1837                 $sql_indexes_query = '';
 1838 
 1839                 if (! empty($indexes)) {
 1840                     $sql_indexes_query .= $alter_header . $crlf . '  ADD '
 1841                         . implode(',' . $crlf . '  ADD ', $indexes)
 1842                         . $alter_footer;
 1843                 }
 1844 
 1845                 if (! empty($indexes_fulltext)) {
 1846                     // InnoDB supports one FULLTEXT index creation at a time.
 1847                     // So FULLTEXT indexes are created one-by-one after other
 1848                     // indexes where created.
 1849                     $sql_indexes_query .= $alter_header .
 1850                         ' ADD ' . implode(
 1851                             $alter_footer . $alter_header . ' ADD ',
 1852                             $indexes_fulltext
 1853                         ) . $alter_footer;
 1854                 }
 1855 
 1856                 if (! empty($indexes) || ! empty($indexes_fulltext)) {
 1857                     $sql_indexes = $this->generateComment(
 1858                         $crlf,
 1859                         $sql_indexes,
 1860                         __('Indexes for dumped tables'),
 1861                         __('Indexes for table'),
 1862                         $table_alias,
 1863                         $compat
 1864                     ) . $sql_indexes_query;
 1865                 }
 1866 
 1867                 // Generating drop foreign keys-related query.
 1868                 if (! empty($dropped)) {
 1869                     $sql_drop_foreign_keys = $alter_header . $crlf . '  DROP '
 1870                         . implode(',' . $crlf . '  DROP ', $dropped)
 1871                         . $alter_footer;
 1872                 }
 1873 
 1874                 // Generating auto-increment-related query.
 1875                 if (! empty($auto_increment) && $update_indexes_increments) {
 1876                     $sql_auto_increments_query = $alter_header . $crlf . '  MODIFY '
 1877                         . implode(',' . $crlf . '  MODIFY ', $auto_increment);
 1878                     if (isset($GLOBALS['sql_auto_increment'])
 1879                         && ($statement->entityOptions->has('AUTO_INCREMENT') !== false)
 1880                     ) {
 1881                         if (! isset($GLOBALS['table_data'])
 1882                             || (isset($GLOBALS['table_data'])
 1883                             && in_array($table, $GLOBALS['table_data']))
 1884                         ) {
 1885                             $sql_auto_increments_query .= ', AUTO_INCREMENT='
 1886                                 . $statement->entityOptions->has('AUTO_INCREMENT');
 1887                         }
 1888                     }
 1889                     $sql_auto_increments_query .= ';' . $crlf;
 1890 
 1891                     $sql_auto_increments = $this->generateComment(
 1892                         $crlf,
 1893                         $sql_auto_increments,
 1894                         __('AUTO_INCREMENT for dumped tables'),
 1895                         __('AUTO_INCREMENT for table'),
 1896                         $table_alias,
 1897                         $compat
 1898                     ) . $sql_auto_increments_query;
 1899                 }
 1900 
 1901                 // Removing the `AUTO_INCREMENT` attribute from the `CREATE TABLE`
 1902                 // too.
 1903                 if (! empty($statement->entityOptions)
 1904                     && (empty($GLOBALS['sql_if_not_exists'])
 1905                     || empty($GLOBALS['sql_auto_increment']))
 1906                 ) {
 1907                     $statement->entityOptions->remove('AUTO_INCREMENT');
 1908                 }
 1909 
 1910                 // Rebuilding the query.
 1911                 $create_query = $statement->build();
 1912             }
 1913 
 1914             $schema_create .= $create_query;
 1915         }
 1916 
 1917         $dbi->freeResult($result);
 1918 
 1919         // Restoring old mode.
 1920         Context::$MODE = $old_mode;
 1921 
 1922         return $warning . $schema_create . ($add_semicolon ? ';' . $crlf : '');
 1923     }
 1924 
 1925     /**
 1926      * Returns $table's comments, relations etc.
 1927      *
 1928      * @param string $db          database name
 1929      * @param string $table       table name
 1930      * @param string $crlf        end of line sequence
 1931      * @param bool   $do_relation whether to include relation comments
 1932      * @param bool   $do_mime     whether to include mime comments
 1933      * @param array  $aliases     Aliases of db/table/columns
 1934      *
 1935      * @return string resulting comments
 1936      */
 1937     private function getTableComments(
 1938         $db,
 1939         $table,
 1940         $crlf,
 1941         $do_relation = false,
 1942         $do_mime = false,
 1943         array $aliases = []
 1944     ) {
 1945         global $cfgRelation, $sql_backquotes;
 1946 
 1947         $db_alias = $db;
 1948         $table_alias = $table;
 1949         $this->initAlias($aliases, $db_alias, $table_alias);
 1950 
 1951         $schema_create = '';
 1952 
 1953         // Check if we can use Relations
 1954         [$res_rel, $have_rel] = $this->relation->getRelationsAndStatus(
 1955             $do_relation && ! empty($cfgRelation['relation']),
 1956             $db,
 1957             $table
 1958         );
 1959 
 1960         if ($do_mime && $cfgRelation['mimework']) {
 1961             $mime_map = $this->transformations->getMime($db, $table, true);
 1962             if ($mime_map === null) {
 1963                 unset($mime_map);
 1964             }
 1965         }
 1966 
 1967         if (isset($mime_map) && count($mime_map) > 0) {
 1968             $schema_create .= $this->possibleCRLF()
 1969                 . $this->exportComment()
 1970                 . $this->exportComment(
 1971                     __('MEDIA TYPES FOR TABLE') . ' '
 1972                     . Util::backquote($table, $sql_backquotes) . ':'
 1973                 );
 1974             foreach ($mime_map as $mime_field => $mime) {
 1975                 $schema_create .= $this->exportComment(
 1976                     '  '
 1977                     . Util::backquote($mime_field, $sql_backquotes)
 1978                 )
 1979                 . $this->exportComment(
 1980                     '      '
 1981                     . Util::backquote(
 1982                         $mime['mimetype'],
 1983                         $sql_backquotes
 1984                     )
 1985                 );
 1986             }
 1987             $schema_create .= $this->exportComment();
 1988         }
 1989 
 1990         if ($have_rel) {
 1991             $schema_create .= $this->possibleCRLF()
 1992                 . $this->exportComment()
 1993                 . $this->exportComment(
 1994                     __('RELATIONSHIPS FOR TABLE') . ' '
 1995                     . Util::backquote($table_alias, $sql_backquotes)
 1996                     . ':'
 1997                 );
 1998 
 1999             foreach ($res_rel as $rel_field => $rel) {
 2000                 if ($rel_field !== 'foreign_keys_data') {
 2001                     $rel_field_alias = ! empty(
 2002                         $aliases[$db]['tables'][$table]['columns'][$rel_field]
 2003                     ) ? $aliases[$db]['tables'][$table]['columns'][$rel_field]
 2004                         : $rel_field;
 2005                     $schema_create .= $this->exportComment(
 2006                         '  '
 2007                         . Util::backquote(
 2008                             $rel_field_alias,
 2009                             $sql_backquotes
 2010                         )
 2011                     )
 2012                     . $this->exportComment(
 2013                         '      '
 2014                         . Util::backquote(
 2015                             $rel['foreign_table'],
 2016                             $sql_backquotes
 2017                         )
 2018                         . ' -> '
 2019                         . Util::backquote(
 2020                             $rel['foreign_field'],
 2021                             $sql_backquotes
 2022                         )
 2023                     );
 2024                 } else {
 2025                     foreach ($rel as $one_key) {
 2026                         foreach ($one_key['index_list'] as $index => $field) {
 2027                             $rel_field_alias = ! empty(
 2028                                 $aliases[$db]['tables'][$table]['columns'][$field]
 2029                             ) ? $aliases[$db]['tables'][$table]['columns'][$field]
 2030                                 : $field;
 2031                             $schema_create .= $this->exportComment(
 2032                                 '  '
 2033                                 . Util::backquote(
 2034                                     $rel_field_alias,
 2035                                     $sql_backquotes
 2036                                 )
 2037                             )
 2038                             . $this->exportComment(
 2039                                 '      '
 2040                                 . Util::backquote(
 2041                                     $one_key['ref_table_name'],
 2042                                     $sql_backquotes
 2043                                 )
 2044                                 . ' -> '
 2045                                 . Util::backquote(
 2046                                     $one_key['ref_index_list'][$index],
 2047                                     $sql_backquotes
 2048                                 )
 2049                             );
 2050                         }
 2051                     }
 2052                 }
 2053             }
 2054             $schema_create .= $this->exportComment();
 2055         }
 2056 
 2057         return $schema_create;
 2058     }
 2059 
 2060     /**
 2061      * Outputs a raw query
 2062      *
 2063      * @param string $err_url   the url to go back in case of error
 2064      * @param string $sql_query the rawquery to output
 2065      * @param string $crlf      the seperator for a file
 2066      *
 2067      * @return bool if succeeded
 2068      */
 2069     public function exportRawQuery(string $err_url, string $sql_query, string $crlf): bool
 2070     {
 2071         return $this->export->outputHandler($sql_query);
 2072     }
 2073 
 2074     /**
 2075      * Outputs table's structure
 2076      *
 2077      * @param string $db          database name
 2078      * @param string $table       table name
 2079      * @param string $crlf        the end of line sequence
 2080      * @param string $error_url   the url to go back in case of error
 2081      * @param string $export_mode 'create_table','triggers','create_view',
 2082      *                            'stand_in'
 2083      * @param string $export_type 'server', 'database', 'table'
 2084      * @param bool   $relation    whether to include relation comments
 2085      * @param bool   $comments    whether to include the pmadb-style column
 2086      *                            comments as comments in the structure; this is
 2087      *                            deprecated but the parameter is left here
 2088      *                            because /export calls exportStructure()
 2089      *                            also for other export types which use this
 2090      *                            parameter
 2091      * @param bool   $mime        whether to include mime comments
 2092      * @param bool   $dates       whether to include creation/update/check dates
 2093      * @param array  $aliases     Aliases of db/table/columns
 2094      *
 2095      * @return bool Whether it succeeded
 2096      */
 2097     public function exportStructure(
 2098         $db,
 2099         $table,
 2100         $crlf,
 2101         $error_url,
 2102         $export_mode,
 2103         $export_type,
 2104         $relation = false,
 2105         $comments = false,
 2106         $mime = false,
 2107         $dates = false,
 2108         array $aliases = []
 2109     ) {
 2110         global $dbi;
 2111 
 2112         $db_alias = $db;
 2113         $table_alias = $table;
 2114         $this->initAlias($aliases, $db_alias, $table_alias);
 2115         if (isset($GLOBALS['sql_compatibility'])) {
 2116             $compat = $GLOBALS['sql_compatibility'];
 2117         } else {
 2118             $compat = 'NONE';
 2119         }
 2120 
 2121         $formatted_table_name = Util::backquoteCompat(
 2122             $table_alias,
 2123             $compat,
 2124             isset($GLOBALS['sql_backquotes'])
 2125         );
 2126         $dump = $this->possibleCRLF()
 2127             . $this->exportComment(str_repeat('-', 56))
 2128             . $this->possibleCRLF()
 2129             . $this->exportComment();
 2130 
 2131         switch ($export_mode) {
 2132             case 'create_table':
 2133                 $dump .= $this->exportComment(
 2134                     __('Table structure for table') . ' ' . $formatted_table_name
 2135                 );
 2136                 $dump .= $this->exportComment();
 2137                 $dump .= $this->getTableDef(
 2138                     $db,
 2139                     $table,
 2140                     $crlf,
 2141                     $error_url,
 2142                     $dates,
 2143                     true,
 2144                     false,
 2145                     true,
 2146                     $aliases
 2147                 );
 2148                 $dump .= $this->getTableComments(
 2149                     $db,
 2150                     $table,
 2151                     $crlf,
 2152                     $relation,
 2153                     $mime,
 2154                     $aliases
 2155                 );
 2156                 break;
 2157             case 'triggers':
 2158                 $dump = '';
 2159                 $delimiter = '$$';
 2160                 $triggers = $dbi->getTriggers($db, $table, $delimiter);
 2161                 if ($triggers) {
 2162                     $dump .= $this->possibleCRLF()
 2163                     . $this->exportComment()
 2164                     . $this->exportComment(
 2165                         __('Triggers') . ' ' . $formatted_table_name
 2166                     )
 2167                         . $this->exportComment();
 2168                     $used_alias = false;
 2169                     $trigger_query = '';
 2170                     foreach ($triggers as $trigger) {
 2171                         if (! empty($GLOBALS['sql_drop_table'])) {
 2172                             $trigger_query .= $trigger['drop'] . ';' . $crlf;
 2173                         }
 2174 
 2175                         $trigger_query .= 'DELIMITER ' . $delimiter . $crlf;
 2176                         $trigger_query .= $this->replaceWithAliases(
 2177                             $trigger['create'],
 2178                             $aliases,
 2179                             $db,
 2180                             $table,
 2181                             $flag
 2182                         );
 2183                         if ($flag) {
 2184                             $used_alias = true;
 2185                         }
 2186                         $trigger_query .= 'DELIMITER ;' . $crlf;
 2187                     }
 2188                     // One warning per table.
 2189                     if ($used_alias) {
 2190                         $dump .= $this->exportComment(
 2191                             __('It appears your table uses triggers;')
 2192                         )
 2193                         . $this->exportComment(
 2194                             __('alias export may not work reliably in all cases.')
 2195                         )
 2196                         . $this->exportComment();
 2197                     }
 2198                     $dump .= $trigger_query;
 2199                 }
 2200                 break;
 2201             case 'create_view':
 2202                 if (empty($GLOBALS['sql_views_as_tables'])) {
 2203                     $dump .= $this->exportComment(
 2204                         __('Structure for view')
 2205                         . ' '
 2206                         . $formatted_table_name
 2207                     )
 2208                     . $this->exportComment();
 2209                     // delete the stand-in table previously created (if any)
 2210                     if ($export_type !== 'table') {
 2211                         $dump .= 'DROP TABLE IF EXISTS '
 2212                             . Util::backquote($table_alias) . ';' . $crlf;
 2213                     }
 2214                     $dump .= $this->getTableDef(
 2215                         $db,
 2216                         $table,
 2217                         $crlf,
 2218                         $error_url,
 2219                         $dates,
 2220                         true,
 2221                         true,
 2222                         true,
 2223                         $aliases
 2224                     );
 2225                 } else {
 2226                     $dump .= $this->exportComment(
 2227                         sprintf(
 2228                             __('Structure for view %s exported as a table'),
 2229                             $formatted_table_name
 2230                         )
 2231                     )
 2232                     . $this->exportComment();
 2233                     // delete the stand-in table previously created (if any)
 2234                     if ($export_type !== 'table') {
 2235                         $dump .= 'DROP TABLE IF EXISTS '
 2236                         . Util::backquote($table_alias) . ';' . $crlf;
 2237                     }
 2238                     $dump .= $this->getTableDefForView(
 2239                         $db,
 2240                         $table,
 2241                         $crlf,
 2242                         true,
 2243                         $aliases
 2244                     );
 2245                 }
 2246                 break;
 2247             case 'stand_in':
 2248                 $dump .= $this->exportComment(
 2249                     __('Stand-in structure for view') . ' ' . $formatted_table_name
 2250                 )
 2251                     . $this->exportComment(
 2252                         __('(See below for the actual view)')
 2253                     )
 2254                     . $this->exportComment();
 2255                 // export a stand-in definition to resolve view dependencies
 2256                 $dump .= $this->getTableDefStandIn($db, $table, $crlf, $aliases);
 2257         }
 2258 
 2259         // this one is built by getTableDef() to use in table copy/move
 2260         // but not in the case of export
 2261         unset($GLOBALS['sql_constraints_query']);
 2262 
 2263         return $this->export->outputHandler($dump);
 2264     }
 2265 
 2266     /**
 2267      * Outputs the content of a table in SQL format
 2268      *
 2269      * @param string $db        database name
 2270      * @param string $table     table name
 2271      * @param string $crlf      the end of line sequence
 2272      * @param string $error_url the url to go back in case of error
 2273      * @param string $sql_query SQL query for obtaining data
 2274      * @param array  $aliases   Aliases of db/table/columns
 2275      *
 2276      * @return bool Whether it succeeded
 2277      */
 2278     public function exportData(
 2279         $db,
 2280         $table,
 2281         $crlf,
 2282         $error_url,
 2283         $sql_query,
 2284         array $aliases = []
 2285     ) {
 2286         global $current_row, $sql_backquotes, $dbi;
 2287 
 2288         // Do not export data for merge tables
 2289         if ($dbi->getTable($db, $table)->isMerge()) {
 2290             return true;
 2291         }
 2292 
 2293         $db_alias = $db;
 2294         $table_alias = $table;
 2295         $this->initAlias($aliases, $db_alias, $table_alias);
 2296 
 2297         if (isset($GLOBALS['sql_compatibility'])) {
 2298             $compat = $GLOBALS['sql_compatibility'];
 2299         } else {
 2300             $compat = 'NONE';
 2301         }
 2302 
 2303         $formatted_table_name = Util::backquoteCompat(
 2304             $table_alias,
 2305             $compat,
 2306             $sql_backquotes
 2307         );
 2308 
 2309         // Do not export data for a VIEW, unless asked to export the view as a table
 2310         // (For a VIEW, this is called only when exporting a single VIEW)
 2311         if ($dbi->getTable($db, $table)->isView()
 2312             && empty($GLOBALS['sql_views_as_tables'])
 2313         ) {
 2314             $head = $this->possibleCRLF()
 2315                 . $this->exportComment()
 2316                 . $this->exportComment('VIEW ' . $formatted_table_name)
 2317                 . $this->exportComment(__('Data:') . ' ' . __('None'))
 2318                 . $this->exportComment()
 2319                 . $this->possibleCRLF();
 2320 
 2321             return $this->export->outputHandler($head);
 2322         }
 2323 
 2324         $result = $dbi->tryQuery(
 2325             $sql_query,
 2326             DatabaseInterface::CONNECT_USER,
 2327             DatabaseInterface::QUERY_UNBUFFERED
 2328         );
 2329         // a possible error: the table has crashed
 2330         $tmp_error = $dbi->getError();
 2331         if ($tmp_error) {
 2332             $message = sprintf(__('Error reading data for table %s:'), $db . '.' . $table);
 2333             $message .= ' ' . $tmp_error;
 2334             if (! defined('TESTSUITE')) {
 2335                 trigger_error($message, E_USER_ERROR);
 2336             }
 2337 
 2338             return $this->export->outputHandler(
 2339                 $this->exportComment($message)
 2340             );
 2341         }
 2342 
 2343         if ($result == false) {
 2344             $dbi->freeResult($result);
 2345 
 2346             return true;
 2347         }
 2348 
 2349         $fields_cnt = $dbi->numFields($result);
 2350 
 2351         // Get field information
 2352         $fields_meta = $dbi->getFieldsMeta($result);
 2353         $field_flags = [];
 2354         for ($j = 0; $j < $fields_cnt; $j++) {
 2355             $field_flags[$j] = $dbi->fieldFlags($result, $j);
 2356         }
 2357 
 2358         $field_set = [];
 2359         for ($j = 0; $j < $fields_cnt; $j++) {
 2360             $col_as = $fields_meta[$j]->name;
 2361             if (! empty($aliases[$db]['tables'][$table]['columns'][$col_as])) {
 2362                 $col_as = $aliases[$db]['tables'][$table]['columns'][$col_as];
 2363             }
 2364             $field_set[$j] = Util::backquoteCompat(
 2365                 $col_as,
 2366                 $compat,
 2367                 $sql_backquotes
 2368             );
 2369         }
 2370 
 2371         if (isset($GLOBALS['sql_type'])
 2372             && $GLOBALS['sql_type'] === 'UPDATE'
 2373         ) {
 2374             // update
 2375             $schema_insert = 'UPDATE ';
 2376             if (isset($GLOBALS['sql_ignore'])) {
 2377                 $schema_insert .= 'IGNORE ';
 2378             }
 2379             // avoid EOL blank
 2380             $schema_insert .= Util::backquoteCompat(
 2381                 $table_alias,
 2382                 $compat,
 2383                 $sql_backquotes
 2384             ) . ' SET';
 2385         } else {
 2386             // insert or replace
 2387             if (isset($GLOBALS['sql_type'])
 2388                 && $GLOBALS['sql_type'] === 'REPLACE'
 2389             ) {
 2390                 $sql_command = 'REPLACE';
 2391             } else {
 2392                 $sql_command = 'INSERT';
 2393             }
 2394 
 2395             // delayed inserts?
 2396             if (isset($GLOBALS['sql_delayed'])) {
 2397                 $insert_delayed = ' DELAYED';
 2398             } else {
 2399                 $insert_delayed = '';
 2400             }
 2401 
 2402             // insert ignore?
 2403             if (isset($GLOBALS['sql_type'], $GLOBALS['sql_ignore']) && $GLOBALS['sql_type'] === 'INSERT') {
 2404                 $insert_delayed .= ' IGNORE';
 2405             }
 2406             //truncate table before insert
 2407             if (isset($GLOBALS['sql_truncate'])
 2408                 && $GLOBALS['sql_truncate']
 2409                 && $sql_command === 'INSERT'
 2410             ) {
 2411                 $truncate = 'TRUNCATE TABLE '
 2412                     . Util::backquoteCompat(
 2413                         $table_alias,
 2414                         $compat,
 2415                         $sql_backquotes
 2416                     ) . ';';
 2417                 $truncatehead = $this->possibleCRLF()
 2418                     . $this->exportComment()
 2419                     . $this->exportComment(
 2420                         __('Truncate table before insert') . ' '
 2421                         . $formatted_table_name
 2422                     )
 2423                     . $this->exportComment()
 2424                     . $crlf;
 2425                 $this->export->outputHandler($truncatehead);
 2426                 $this->export->outputHandler($truncate);
 2427             }
 2428 
 2429             // scheme for inserting fields
 2430             if ($GLOBALS['sql_insert_syntax'] === 'complete'
 2431                 || $GLOBALS['sql_insert_syntax'] === 'both'
 2432             ) {
 2433                 $fields = implode(', ', $field_set);
 2434                 $schema_insert = $sql_command . $insert_delayed . ' INTO '
 2435                     . Util::backquoteCompat(
 2436                         $table_alias,
 2437                         $compat,
 2438                         $sql_backquotes
 2439                     )
 2440                     // avoid EOL blank
 2441                     . ' (' . $fields . ') VALUES';
 2442             } else {
 2443                 $schema_insert = $sql_command . $insert_delayed . ' INTO '
 2444                     . Util::backquoteCompat(
 2445                         $table_alias,
 2446                         $compat,
 2447                         $sql_backquotes
 2448                     )
 2449                     . ' VALUES';
 2450             }
 2451         }
 2452 
 2453         //\x08\\x09, not required
 2454         $current_row = 0;
 2455         $query_size = 0;
 2456         if (($GLOBALS['sql_insert_syntax'] === 'extended'
 2457             || $GLOBALS['sql_insert_syntax'] === 'both')
 2458             && (! isset($GLOBALS['sql_type'])
 2459             || $GLOBALS['sql_type'] !== 'UPDATE')
 2460         ) {
 2461             $separator = ',';
 2462             $schema_insert .= $crlf;
 2463         } else {
 2464             $separator = ';';
 2465         }
 2466 
 2467         while ($row = $dbi->fetchRow($result)) {
 2468             if ($current_row == 0) {
 2469                 $head = $this->possibleCRLF()
 2470                     . $this->exportComment()
 2471                     . $this->exportComment(
 2472                         __('Dumping data for table') . ' '
 2473                         . $formatted_table_name
 2474                     )
 2475                     . $this->exportComment()
 2476                     . $crlf;
 2477                 if (! $this->export->outputHandler($head)) {
 2478                     return false;
 2479                 }
 2480             }
 2481             // We need to SET IDENTITY_INSERT ON for MSSQL
 2482             if (isset($GLOBALS['sql_compatibility'])
 2483                 && $GLOBALS['sql_compatibility'] === 'MSSQL'
 2484                 && $current_row == 0
 2485             ) {
 2486                 if (! $this->export->outputHandler(
 2487                     'SET IDENTITY_INSERT '
 2488                     . Util::backquoteCompat(
 2489                         $table_alias,
 2490                         $compat,
 2491                         $sql_backquotes
 2492                     )
 2493                     . ' ON ;' . $crlf
 2494                 )
 2495                 ) {
 2496                     return false;
 2497                 }
 2498             }
 2499             $current_row++;
 2500             $values = [];
 2501             for ($j = 0; $j < $fields_cnt; $j++) {
 2502                 // NULL
 2503                 if (! isset($row[$j]) || $row[$j] === null) {
 2504                     $values[] = 'NULL';
 2505                 } elseif ($fields_meta[$j]->numeric
 2506                     && $fields_meta[$j]->type !== 'timestamp'
 2507                     && ! $fields_meta[$j]->blob
 2508                 ) {
 2509                     // a number
 2510                     // timestamp is numeric on some MySQL 4.1, BLOBs are
 2511                     // sometimes numeric
 2512                     $values[] = $row[$j];
 2513                 } elseif (stripos($field_flags[$j], 'BINARY') !== false
 2514                     && isset($GLOBALS['sql_hex_for_binary'])
 2515                 ) {
 2516                     // a true BLOB
 2517                     // - mysqldump only generates hex data when the --hex-blob
 2518                     //   option is used, for fields having the binary attribute
 2519                     //   no hex is generated
 2520                     // - a TEXT field returns type blob but a real blob
 2521                     //   returns also the 'binary' flag
 2522 
 2523                     // empty blobs need to be different, but '0' is also empty
 2524                     // :-(
 2525                     if (empty($row[$j]) && $row[$j] != '0') {
 2526                         $values[] = '\'\'';
 2527                     } else {
 2528                         $values[] = '0x' . bin2hex($row[$j]);
 2529                     }
 2530                 } elseif ($fields_meta[$j]->type === 'bit') {
 2531                     // detection of 'bit' works only on mysqli extension
 2532                     $values[] = "b'" . $dbi->escapeString(
 2533                         Util::printableBitValue(
 2534                             (int) $row[$j],
 2535                             (int) $fields_meta[$j]->length
 2536                         )
 2537                     )
 2538                     . "'";
 2539                 } elseif ($fields_meta[$j]->type === 'geometry') {
 2540                     // export GIS types as hex
 2541                     $values[] = '0x' . bin2hex($row[$j]);
 2542                 } elseif (! empty($GLOBALS['exporting_metadata'])
 2543                     && $row[$j] === '@LAST_PAGE'
 2544                 ) {
 2545                     $values[] = '@LAST_PAGE';
 2546                 } else {
 2547                     // something else -> treat as a string
 2548                     $values[] = '\''
 2549                         . $dbi->escapeString($row[$j])
 2550                         . '\'';
 2551                 }
 2552             }
 2553 
 2554             // should we make update?
 2555             if (isset($GLOBALS['sql_type'])
 2556                 && $GLOBALS['sql_type'] === 'UPDATE'
 2557             ) {
 2558                 $insert_line = $schema_insert;
 2559                 for ($i = 0; $i < $fields_cnt; $i++) {
 2560                     if ($i == 0) {
 2561                         $insert_line .= ' ';
 2562                     }
 2563                     if ($i > 0) {
 2564                         // avoid EOL blank
 2565                         $insert_line .= ',';
 2566                     }
 2567                     $insert_line .= $field_set[$i] . ' = ' . $values[$i];
 2568                 }
 2569 
 2570                 [$tmp_unique_condition, $tmp_clause_is_unique] = Util::getUniqueCondition(
 2571                     $result,
 2572                     $fields_cnt,
 2573                     $fields_meta,
 2574                     $row
 2575                 );
 2576                 $insert_line .= ' WHERE ' . $tmp_unique_condition;
 2577                 unset($tmp_unique_condition, $tmp_clause_is_unique);
 2578             } else {
 2579                 // Extended inserts case
 2580                 if ($GLOBALS['sql_insert_syntax'] === 'extended'
 2581                     || $GLOBALS['sql_insert_syntax'] === 'both'
 2582                 ) {
 2583                     if ($current_row == 1) {
 2584                         $insert_line = $schema_insert . '('
 2585                             . implode(', ', $values) . ')';
 2586                     } else {
 2587                         $insert_line = '(' . implode(', ', $values) . ')';
 2588                         $insertLineSize = mb_strlen($insert_line);
 2589                         $sql_max_size = $GLOBALS['sql_max_query_size'];
 2590                         if (isset($sql_max_size)
 2591                             && $sql_max_size > 0
 2592                             && $query_size + $insertLineSize > $sql_max_size
 2593                         ) {
 2594                             if (! $this->export->outputHandler(';' . $crlf)) {
 2595                                 return false;
 2596                             }
 2597                             $query_size = 0;
 2598                             $current_row = 1;
 2599                             $insert_line = $schema_insert . $insert_line;
 2600                         }
 2601                     }
 2602                     $query_size += mb_strlen($insert_line);
 2603                     // Other inserts case
 2604                 } else {
 2605                     $insert_line = $schema_insert
 2606                         . '(' . implode(', ', $values) . ')';
 2607                 }
 2608             }
 2609             unset($values);
 2610 
 2611             if (! $this->export->outputHandler(
 2612                 ($current_row == 1 ? '' : $separator . $crlf)
 2613                 . $insert_line
 2614             )
 2615             ) {
 2616                 return false;
 2617             }
 2618         }
 2619 
 2620         if ($current_row > 0) {
 2621             if (! $this->export->outputHandler(';' . $crlf)) {
 2622                 return false;
 2623             }
 2624         }
 2625 
 2626         // We need to SET IDENTITY_INSERT OFF for MSSQL
 2627         if (isset($GLOBALS['sql_compatibility'])
 2628             && $GLOBALS['sql_compatibility'] === 'MSSQL'
 2629             && $current_row > 0
 2630         ) {
 2631             $outputSucceeded = $this->export->outputHandler(
 2632                 $crlf . 'SET IDENTITY_INSERT '
 2633                 . Util::backquoteCompat(
 2634                     $table_alias,
 2635                     $compat,
 2636                     $sql_backquotes
 2637                 )
 2638                 . ' OFF;' . $crlf
 2639             );
 2640             if (! $outputSucceeded) {
 2641                 return false;
 2642             }
 2643         }
 2644 
 2645         $dbi->freeResult($result);
 2646 
 2647         return true;
 2648     }
 2649 
 2650     /**
 2651      * Make a create table statement compatible with MSSQL
 2652      *
 2653      * @param string $create_query MySQL create table statement
 2654      *
 2655      * @return string MSSQL compatible create table statement
 2656      */
 2657     private function makeCreateTableMSSQLCompatible($create_query)
 2658     {
 2659         // In MSSQL
 2660         // 1. No 'IF NOT EXISTS' in CREATE TABLE
 2661         // 2. DATE field doesn't exists, we will use DATETIME instead
 2662         // 3. UNSIGNED attribute doesn't exist
 2663         // 4. No length on INT, TINYINT, SMALLINT, BIGINT and no precision on
 2664         //    FLOAT fields
 2665         // 5. No KEY and INDEX inside CREATE TABLE
 2666         // 6. DOUBLE field doesn't exists, we will use FLOAT instead
 2667 
 2668         $create_query = (string) preg_replace(
 2669             '/^CREATE TABLE IF NOT EXISTS/',
 2670             'CREATE TABLE',
 2671             (string) $create_query
 2672         );
 2673         // first we need  to replace all lines ended with '" DATE ...,\n'
 2674         // last preg_replace preserve us from situation with date text
 2675         // inside DEFAULT field value
 2676         $create_query = (string) preg_replace(
 2677             "/\" date DEFAULT NULL(,)?\n/",
 2678             '" datetime DEFAULT NULL$1' . "\n",
 2679             $create_query
 2680         );
 2681         $create_query = (string) preg_replace(
 2682             "/\" date NOT NULL(,)?\n/",
 2683             '" datetime NOT NULL$1' . "\n",
 2684             $create_query
 2685         );
 2686         $create_query = (string) preg_replace(
 2687             '/" date NOT NULL DEFAULT \'([^\'])/',
 2688             '" datetime NOT NULL DEFAULT \'$1',
 2689             $create_query
 2690         );
 2691 
 2692         // next we need to replace all lines ended with ') UNSIGNED ...,'
 2693         // last preg_replace preserve us from situation with unsigned text
 2694         // inside DEFAULT field value
 2695         $create_query = (string) preg_replace(
 2696             "/\) unsigned NOT NULL(,)?\n/",
 2697             ') NOT NULL$1' . "\n",
 2698             $create_query
 2699         );
 2700         $create_query = (string) preg_replace(
 2701             "/\) unsigned DEFAULT NULL(,)?\n/",
 2702             ') DEFAULT NULL$1' . "\n",
 2703             $create_query
 2704         );
 2705         $create_query = (string) preg_replace(
 2706             '/\) unsigned NOT NULL DEFAULT \'([^\'])/',
 2707             ') NOT NULL DEFAULT \'$1',
 2708             $create_query
 2709         );
 2710 
 2711         // we need to replace all lines ended with
 2712         // '" INT|TINYINT([0-9]{1,}) ...,' last preg_replace preserve us
 2713         // from situation with int([0-9]{1,}) text inside DEFAULT field
 2714         // value
 2715         $create_query = (string) preg_replace(
 2716             '/" (int|tinyint|smallint|bigint)\([0-9]+\) DEFAULT NULL(,)?\n/',
 2717             '" $1 DEFAULT NULL$2' . "\n",
 2718             $create_query
 2719         );
 2720         $create_query = (string) preg_replace(
 2721             '/" (int|tinyint|smallint|bigint)\([0-9]+\) NOT NULL(,)?\n/',
 2722             '" $1 NOT NULL$2' . "\n",
 2723             $create_query
 2724         );
 2725         $create_query = (string) preg_replace(
 2726             '/" (int|tinyint|smallint|bigint)\([0-9]+\) NOT NULL DEFAULT \'([^\'])/',
 2727             '" $1 NOT NULL DEFAULT \'$2',
 2728             $create_query
 2729         );
 2730 
 2731         // we need to replace all lines ended with
 2732         // '" FLOAT|DOUBLE([0-9,]{1,}) ...,'
 2733         // last preg_replace preserve us from situation with
 2734         // float([0-9,]{1,}) text inside DEFAULT field value
 2735         $create_query = (string) preg_replace(
 2736             '/" (float|double)(\([0-9]+,[0-9,]+\))? DEFAULT NULL(,)?\n/',
 2737             '" float DEFAULT NULL$3' . "\n",
 2738             $create_query
 2739         );
 2740         $create_query = (string) preg_replace(
 2741             '/" (float|double)(\([0-9,]+,[0-9,]+\))? NOT NULL(,)?\n/',
 2742             '" float NOT NULL$3' . "\n",
 2743             $create_query
 2744         );
 2745 
 2746         return (string) preg_replace(
 2747             '/" (float|double)(\([0-9,]+,[0-9,]+\))? NOT NULL DEFAULT \'([^\'])/',
 2748             '" float NOT NULL DEFAULT \'$3',
 2749             $create_query
 2750         );
 2751 
 2752         // @todo remove indexes from CREATE TABLE
 2753     }
 2754 
 2755     /**
 2756      * replaces db/table/column names with their aliases
 2757      *
 2758      * @param string $sql_query SQL query in which aliases are to be substituted
 2759      * @param array  $aliases   Alias information for db/table/column
 2760      * @param string $db        the database name
 2761      * @param string $table     the tablename
 2762      * @param string $flag      the flag denoting whether any replacement was done
 2763      *
 2764      * @return string query replaced with aliases
 2765      */
 2766     public function replaceWithAliases(
 2767         $sql_query,
 2768         array $aliases,
 2769         $db,
 2770         $table = '',
 2771         &$flag = null
 2772     ) {
 2773         $flag = false;
 2774 
 2775         /**
 2776          * The parser of this query.
 2777          *
 2778          * @var Parser $parser
 2779          */
 2780         $parser = new Parser($sql_query);
 2781 
 2782         if (empty($parser->statements[0])) {
 2783             return $sql_query;
 2784         }
 2785 
 2786         /**
 2787          * The statement that represents the query.
 2788          *
 2789          * @var CreateStatement $statement
 2790          */
 2791         $statement = $parser->statements[0];
 2792 
 2793         /**
 2794          * Old database name.
 2795          *
 2796          * @var string $old_database
 2797          */
 2798         $old_database = $db;
 2799 
 2800         // Replacing aliases in `CREATE TABLE` statement.
 2801         if ($statement->options->has('TABLE')) {
 2802             // Extracting the name of the old database and table from the
 2803             // statement to make sure the parameters are correct.
 2804             if (! empty($statement->name->database)) {
 2805                 $old_database = $statement->name->database;
 2806             }
 2807 
 2808             /**
 2809              * Old table name.
 2810              *
 2811              * @var string $old_table
 2812              */
 2813             $old_table = $statement->name->table;
 2814 
 2815             // Finding the aliased database name.
 2816             // The database might be empty so we have to add a few checks.
 2817             $new_database = null;
 2818             if (! empty($statement->name->database)) {
 2819                 $new_database = $statement->name->database;
 2820                 if (! empty($aliases[$old_database]['alias'])) {
 2821                     $new_database = $aliases[$old_database]['alias'];
 2822                 }
 2823             }
 2824 
 2825             // Finding the aliases table name.
 2826             $new_table = $old_table;
 2827             if (! empty($aliases[$old_database]['tables'][$old_table]['alias'])) {
 2828                 $new_table = $aliases[$old_database]['tables'][$old_table]['alias'];
 2829             }
 2830 
 2831             // Replacing new values.
 2832             if (($statement->name->database !== $new_database)
 2833                 || ($statement->name->table !== $new_table)
 2834             ) {
 2835                 $statement->name->database = $new_database;
 2836                 $statement->name->table = $new_table;
 2837                 $statement->name->expr = ''; // Force rebuild.
 2838                 $flag = true;
 2839             }
 2840 
 2841             /** @var CreateDefinition $field */
 2842             foreach ($statement->fields as $field) {
 2843                 // Column name.
 2844                 if (! empty($field->type)) {
 2845                     if (! empty($aliases[$old_database]['tables'][$old_table]['columns'][$field->name])) {
 2846                         $field->name = $aliases[$old_database]['tables'][$old_table]['columns'][$field->name];
 2847                         $flag = true;
 2848                     }
 2849                 }
 2850 
 2851                 // Key's columns.
 2852                 if (! empty($field->key)) {
 2853                     foreach ($field->key->columns as $key => $column) {
 2854                         if (empty($aliases[$old_database]['tables'][$old_table]['columns'][$column['name']])) {
 2855                             continue;
 2856                         }
 2857 
 2858                         $columnAliases = $aliases[$old_database]['tables'][$old_table]['columns'];
 2859                         $field->key->columns[$key]['name'] = $columnAliases[$column['name']];
 2860                         $flag = true;
 2861                     }
 2862                 }
 2863 
 2864                 // References.
 2865                 if (empty($field->references)) {
 2866                     continue;
 2867                 }
 2868 
 2869                 $ref_table = $field->references->table->table;
 2870                 // Replacing table.
 2871                 if (! empty($aliases[$old_database]['tables'][$ref_table]['alias'])) {
 2872                     $field->references->table->table
 2873                         = $aliases[$old_database]['tables'][$ref_table]['alias'];
 2874                     $field->references->table->expr = '';
 2875                     $flag = true;
 2876                 }
 2877                 // Replacing column names.
 2878                 foreach ($field->references->columns as $key => $column) {
 2879                     if (empty($aliases[$old_database]['tables'][$ref_table]['columns'][$column])) {
 2880                         continue;
 2881                     }
 2882 
 2883                     $field->references->columns[$key]
 2884                         = $aliases[$old_database]['tables'][$ref_table]['columns'][$column];
 2885                     $flag = true;
 2886                 }
 2887             }
 2888         } elseif ($statement->options->has('TRIGGER')) {
 2889             // Extracting the name of the old database and table from the
 2890             // statement to make sure the parameters are correct.
 2891             if (! empty($statement->table->database)) {
 2892                 $old_database = $statement->table->database;
 2893             }
 2894 
 2895             /**
 2896              * Old table name.
 2897              *
 2898              * @var string $old_table
 2899              */
 2900             $old_table = $statement->table->table;
 2901 
 2902             if (! empty($aliases[$old_database]['tables'][$old_table]['alias'])) {
 2903                 $statement->table->table
 2904                     = $aliases[$old_database]['tables'][$old_table]['alias'];
 2905                 $statement->table->expr = ''; // Force rebuild.
 2906                 $flag = true;
 2907             }
 2908         }
 2909 
 2910         if ($statement->options->has('TRIGGER')
 2911             || $statement->options->has('PROCEDURE')
 2912             || $statement->options->has('FUNCTION')
 2913             || $statement->options->has('VIEW')
 2914         ) {
 2915             // Replacing the body.
 2916             for ($i = 0, $count = count((array) $statement->body); $i < $count; ++$i) {
 2917 
 2918                 /**
 2919                  * Token parsed at this moment.
 2920                  *
 2921                  * @var Token $token
 2922                  */
 2923                 $token = $statement->body[$i];
 2924 
 2925                 // Replacing only symbols (that are not variables) and unknown
 2926                 // identifiers.
 2927                 $isSymbol = $token->type === Token::TYPE_SYMBOL;
 2928                 $isKeyword = $token->type === Token::TYPE_KEYWORD;
 2929                 $isNone = $token->type === Token::TYPE_NONE;
 2930                 $replaceToken = $isSymbol
 2931                     && (! ($token->flags & Token::FLAG_SYMBOL_VARIABLE))
 2932                     || ($isKeyword
 2933                     && (! ($token->flags & Token::FLAG_KEYWORD_RESERVED))
 2934                     || $isNone);
 2935 
 2936                 if (! $replaceToken) {
 2937                     continue;
 2938                 }
 2939 
 2940                 $alias = $this->getAlias($aliases, $token->value);
 2941                 if (empty($alias)) {
 2942                     continue;
 2943                 }
 2944 
 2945                 // Replacing the token.
 2946                 $token->token = Context::escape($alias);
 2947                 $flag = true;
 2948             }
 2949         }
 2950 
 2951         return $statement->build();
 2952     }
 2953 
 2954     /**
 2955      * Generate comment
 2956      *
 2957      * @param string      $crlf          Carriage return character
 2958      * @param string|null $sql_statement SQL statement
 2959      * @param string      $comment1      Comment for dumped table
 2960      * @param string      $comment2      Comment for current table
 2961      * @param string      $table_alias   Table alias
 2962      * @param string      $compat        Compatibility mode
 2963      *
 2964      * @return string
 2965      */
 2966     protected function generateComment(
 2967         $crlf,
 2968         ?string $sql_statement,
 2969         $comment1,
 2970         $comment2,
 2971         $table_alias,
 2972         $compat
 2973     ) {
 2974         if (! isset($sql_statement)) {
 2975             if (isset($GLOBALS['no_constraints_comments'])) {
 2976                 $sql_statement = '';
 2977             } else {
 2978                 $sql_statement = $crlf
 2979                     . $this->exportComment()
 2980                     . $this->exportComment($comment1)
 2981                     . $this->exportComment();
 2982             }
 2983         }
 2984 
 2985         // comments for current table
 2986         if (! isset($GLOBALS['no_constraints_comments'])) {
 2987             $sql_statement .= $crlf
 2988                 . $this->exportComment()
 2989                 . $this->exportComment(
 2990                     $comment2 . ' ' . Util::backquoteCompat(
 2991                         $table_alias,
 2992                         $compat,
 2993                         isset($GLOBALS['sql_backquotes'])
 2994                     )
 2995                 )
 2996                 . $this->exportComment();
 2997         }
 2998 
 2999         return $sql_statement;
 3000     }
 3001 }