Update website
This commit is contained in:
parent
a0b0d3dae7
commit
ae7ef6ad45
3151 changed files with 566766 additions and 48 deletions
698
admin/phpMyAdmin/libraries/advisory_rules_generic.php
Normal file
698
admin/phpMyAdmin/libraries/advisory_rules_generic.php
Normal file
|
@ -0,0 +1,698 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
// Queries
|
||||
[
|
||||
'id' => 'Uptime below one day',
|
||||
'name' => __('Uptime below one day'),
|
||||
'formula' => 'Uptime',
|
||||
'test' => 'value < 86400',
|
||||
'issue' => __('Uptime is less than 1 day, performance tuning may not be accurate.'),
|
||||
'recommendation' => __(
|
||||
'To have more accurate averages it is recommended to let the server run for'
|
||||
. ' longer than a day before running this analyzer'
|
||||
),
|
||||
'justification' => __('The uptime is only %s'),
|
||||
'justification_formula' => 'ADVISOR_timespanFormat(Uptime)',
|
||||
],
|
||||
[
|
||||
'id' => 'Questions below 1,000',
|
||||
'name' => __('Questions below 1,000'),
|
||||
'formula' => 'Questions',
|
||||
'test' => 'value < 1000',
|
||||
'issue' => __(
|
||||
'Fewer than 1,000 questions have been run against this server.'
|
||||
. ' The recommendations may not be accurate.'
|
||||
),
|
||||
'recommendation' => __(
|
||||
'Let the server run for a longer time until it has executed a greater amount of queries.'
|
||||
),
|
||||
'justification' => __('Current amount of Questions: %s'),
|
||||
'justification_formula' => 'Questions',
|
||||
],
|
||||
[
|
||||
'id' => 'Percentage of slow queries',
|
||||
'name' => __('Percentage of slow queries'),
|
||||
'precondition' => 'Questions > 0',
|
||||
'formula' => 'Slow_queries / Questions * 100',
|
||||
'test' => 'value >= 5',
|
||||
'issue' => __('There is a lot of slow queries compared to the overall amount of Queries.'),
|
||||
'recommendation' => __(
|
||||
'You might want to increase {long_query_time}'
|
||||
. ' or optimize the queries listed in the slow query log'
|
||||
),
|
||||
'justification' => __('The slow query rate should be below 5%%, your value is %s%%.'),
|
||||
'justification_formula' => 'round(value,2)',
|
||||
],
|
||||
[
|
||||
'id' => 'Slow query rate',
|
||||
'name' => __('Slow query rate'),
|
||||
'precondition' => 'Questions > 0',
|
||||
'formula' => '(Slow_queries / Questions * 100) / Uptime',
|
||||
'test' => 'value * 60 * 60 > 1',
|
||||
'issue' => __('There is a high percentage of slow queries compared to the server uptime.'),
|
||||
'recommendation' => __(
|
||||
'You might want to increase {long_query_time}'
|
||||
. ' or optimize the queries listed in the slow query log'
|
||||
),
|
||||
'justification' => __('You have a slow query rate of %s per hour, you should have less than 1%% per hour.'),
|
||||
'justification_formula' => 'ADVISOR_bytime(value,2)',
|
||||
],
|
||||
[
|
||||
'id' => 'Long query time',
|
||||
'name' => __('Long query time'),
|
||||
'formula' => 'long_query_time',
|
||||
'test' => 'value >= 10',
|
||||
'issue' => __(
|
||||
'{long_query_time} is set to 10 seconds or more,'
|
||||
. ' thus only slow queries that take above 10 seconds are logged.'
|
||||
),
|
||||
'recommendation' => __(
|
||||
'It is suggested to set {long_query_time} to a lower value, depending on your environment.'
|
||||
. ' Usually a value of 1-5 seconds is suggested.'
|
||||
),
|
||||
'justification' => __('long_query_time is currently set to %ds.'),
|
||||
'justification_formula' => 'value',
|
||||
],
|
||||
[
|
||||
'id' => 'Slow query logging',
|
||||
'name' => __('Slow query logging'),
|
||||
'precondition' => 'PMA_MYSQL_INT_VERSION < 50600',
|
||||
'formula' => 'log_slow_queries',
|
||||
'test' => 'value == \'OFF\'',
|
||||
'issue' => __('The slow query log is disabled.'),
|
||||
'recommendation' => __(
|
||||
'Enable slow query logging by setting {log_slow_queries} to \'ON\'.'
|
||||
. ' This will help troubleshooting badly performing queries.'
|
||||
),
|
||||
'justification' => __('log_slow_queries is set to \'OFF\''),
|
||||
],
|
||||
[
|
||||
'id' => 'Slow query logging',
|
||||
'name' => __('Slow query logging'),
|
||||
'precondition' => 'PMA_MYSQL_INT_VERSION >= 50600',
|
||||
'formula' => 'slow_query_log',
|
||||
'test' => 'value == \'OFF\'',
|
||||
'issue' => __('The slow query log is disabled.'),
|
||||
'recommendation' => __(
|
||||
'Enable slow query logging by setting {slow_query_log} to \'ON\'.'
|
||||
. ' This will help troubleshooting badly performing queries.'
|
||||
),
|
||||
'justification' => __('slow_query_log is set to \'OFF\''),
|
||||
],
|
||||
// Versions
|
||||
[
|
||||
'id' => 'Release Series',
|
||||
'name' => __('Release Series'),
|
||||
'formula' => 'version',
|
||||
'test' => 'substr(value,0,2) <= \'5.\' && substr(value,2,1) < 1',
|
||||
'issue' => __('The MySQL server version less than 5.1.'),
|
||||
'recommendation' => __(
|
||||
'You should upgrade, as MySQL 5.1 has improved performance, and MySQL 5.5 even more so.'
|
||||
),
|
||||
'justification' => __('Current version: %s'),
|
||||
'justification_formula' => 'value',
|
||||
],
|
||||
[
|
||||
'id' => 'Minor Version',
|
||||
'name' => __('Minor Version'),
|
||||
'precondition' => '! fired(\'Release Series\')',
|
||||
'formula' => 'version',
|
||||
'test' => 'substr(value,0,2) <= \'5.\' && substr(value,2,1) <= 1 && substr(value,4,2) < 30',
|
||||
'issue' => __('Version less than 5.1.30 (the first GA release of 5.1).'),
|
||||
'recommendation' => __(
|
||||
'You should upgrade, as recent versions of MySQL 5.1 have improved performance'
|
||||
. ' and MySQL 5.5 even more so.'
|
||||
),
|
||||
'justification' => __('Current version: %s'),
|
||||
'justification_formula' => 'value',
|
||||
],
|
||||
[
|
||||
'id' => 'Minor Version',
|
||||
'name' => __('Minor Version'),
|
||||
'precondition' => '! fired(\'Release Series\')',
|
||||
'formula' => 'version',
|
||||
'test' => 'substr(value,0,1) == 5 && substr(value,2,1) == 5 && substr(value,4,2) < 8',
|
||||
'issue' => __('Version less than 5.5.8 (the first GA release of 5.5).'),
|
||||
'recommendation' => __('You should upgrade, to a stable version of MySQL 5.5.'),
|
||||
'justification' => __('Current version: %s'),
|
||||
'justification_formula' => 'value',
|
||||
],
|
||||
[
|
||||
'id' => 'Distribution',
|
||||
'name' => __('Distribution'),
|
||||
'formula' => 'version_comment',
|
||||
'test' => 'preg_match(\'/source/i\',value)',
|
||||
'issue' => __('Version is compiled from source, not a MySQL official binary.'),
|
||||
'recommendation' => __(
|
||||
'If you did not compile from source, you may be using a package modified by a distribution.'
|
||||
. ' The MySQL manual only is accurate for official MySQL binaries,'
|
||||
. ' not any package distributions (such as RedHat, Debian/Ubuntu etc).'
|
||||
),
|
||||
'justification' => __('\'source\' found in version_comment'),
|
||||
],
|
||||
[
|
||||
'id' => 'Distribution',
|
||||
'name' => __('Distribution'),
|
||||
'formula' => 'version_comment',
|
||||
'test' => 'preg_match(\'/percona/i\',value)',
|
||||
'issue' => __('The MySQL manual only is accurate for official MySQL binaries.'),
|
||||
'recommendation' => __(
|
||||
'Percona documentation is at <a href="https://www.percona.com/software/documentation/">'
|
||||
. 'https://www.percona.com/software/documentation/</a>'
|
||||
),
|
||||
'justification' => __('\'percona\' found in version_comment'),
|
||||
],
|
||||
[
|
||||
'id' => 'MySQL Architecture',
|
||||
'name' => __('MySQL Architecture'),
|
||||
'formula' => 'system_memory',
|
||||
'test' => 'value > 3072*1024 && !preg_match(\'/64/\',version_compile_machine)'
|
||||
. ' && !preg_match(\'/64/\',version_compile_os)',
|
||||
'issue' => __('MySQL is not compiled as a 64-bit package.'),
|
||||
'recommendation' => __(
|
||||
'Your memory capacity is above 3 GiB (assuming the Server is on localhost),'
|
||||
. ' so MySQL might not be able to access all of your memory.'
|
||||
. ' You might want to consider installing the 64-bit version of MySQL.'
|
||||
),
|
||||
'justification' => __('Available memory on this host: %s'),
|
||||
'justification_formula' => 'ADVISOR_formatByteDown(value*1024, 2, 2)',
|
||||
],
|
||||
// Query cache
|
||||
[
|
||||
'id' => 'Query caching method',
|
||||
'name' => __('Query caching method'),
|
||||
'precondition' => '!fired(\'Query cache disabled\')',
|
||||
'formula' => 'Questions / Uptime',
|
||||
'test' => 'value > 100',
|
||||
'issue' => __('Suboptimal caching method.'),
|
||||
'recommendation' => __(
|
||||
'You are using the MySQL Query cache with a fairly high traffic database.'
|
||||
. ' It might be worth considering to use '
|
||||
. '<a href="https://dev.mysql.com/doc/refman/5.6/en/ha-memcached.html">memcached</a>'
|
||||
. ' instead of the MySQL Query cache, especially if you have multiple slaves.'
|
||||
),
|
||||
'justification' => __(
|
||||
'The query cache is enabled and the server receives %d queries per second.'
|
||||
. ' This rule fires if there is more than 100 queries per second.'
|
||||
),
|
||||
'justification_formula' => 'round(value,1)',
|
||||
],
|
||||
// Sorts
|
||||
[
|
||||
'id' => 'Percentage of sorts that cause temporary tables',
|
||||
'name' => __('Percentage of sorts that cause temporary tables'),
|
||||
'precondition' => 'Sort_scan + Sort_range > 0',
|
||||
'formula' => 'Sort_merge_passes / (Sort_scan + Sort_range) * 100',
|
||||
'test' => 'value > 10',
|
||||
'issue' => __('Too many sorts are causing temporary tables.'),
|
||||
'recommendation' => __(
|
||||
'Consider increasing {sort_buffer_size} and/or {read_rnd_buffer_size},'
|
||||
. ' depending on your system memory limits.'
|
||||
),
|
||||
'justification' => __('%s%% of all sorts cause temporary tables, this value should be lower than 10%%.'),
|
||||
'justification_formula' => 'round(value,1)',
|
||||
],
|
||||
[
|
||||
'id' => 'Rate of sorts that cause temporary tables',
|
||||
'name' => __('Rate of sorts that cause temporary tables'),
|
||||
'formula' => 'Sort_merge_passes / Uptime',
|
||||
'test' => 'value * 60 * 60 > 1',
|
||||
'issue' => __('Too many sorts are causing temporary tables.'),
|
||||
'recommendation' => __(
|
||||
'Consider increasing {sort_buffer_size} and/or {read_rnd_buffer_size},'
|
||||
. ' depending on your system memory limits.'
|
||||
),
|
||||
'justification' => __('Temporary tables average: %s, this value should be less than 1 per hour.'),
|
||||
'justification_formula' => 'ADVISOR_bytime(value,2)',
|
||||
],
|
||||
[
|
||||
'id' => 'Sort rows',
|
||||
'name' => __('Sort rows'),
|
||||
'formula' => 'Sort_rows / Uptime',
|
||||
'test' => 'value * 60 >= 1',
|
||||
'issue' => __('There are lots of rows being sorted.'),
|
||||
'recommendation' => __(
|
||||
'While there is nothing wrong with a high amount of row sorting, you might want to'
|
||||
. ' make sure that the queries which require a lot of sorting use indexed columns in'
|
||||
. ' the ORDER BY clause, as this will result in much faster sorting.'
|
||||
),
|
||||
'justification' => __('Sorted rows average: %s'),
|
||||
'justification_formula' => 'ADVISOR_bytime(value,2)',
|
||||
],
|
||||
// Joins, scans
|
||||
[
|
||||
'id' => 'Rate of joins without indexes',
|
||||
'name' => __('Rate of joins without indexes'),
|
||||
'formula' => '(Select_range_check + Select_scan + Select_full_join) / Uptime',
|
||||
'test' => 'value * 60 * 60 > 1',
|
||||
'issue' => __('There are too many joins without indexes.'),
|
||||
'recommendation' => __(
|
||||
'This means that joins are doing full table scans. Adding indexes for the columns being'
|
||||
. ' used in the join conditions will greatly speed up table joins.'
|
||||
),
|
||||
'justification' => __('Table joins average: %s, this value should be less than 1 per hour'),
|
||||
'justification_formula' => 'ADVISOR_bytime(value,2)',
|
||||
],
|
||||
[
|
||||
'id' => 'Rate of reading first index entry',
|
||||
'name' => __('Rate of reading first index entry'),
|
||||
'formula' => 'Handler_read_first / Uptime',
|
||||
'test' => 'value * 60 * 60 > 1',
|
||||
'issue' => __('The rate of reading the first index entry is high.'),
|
||||
'recommendation' => __(
|
||||
'This usually indicates frequent full index scans. Full index scans are faster than'
|
||||
. ' table scans but require lots of CPU cycles in big tables, if those tables that have or'
|
||||
. ' had high volumes of UPDATEs and DELETEs, running \'OPTIMIZE TABLE\' might reduce the'
|
||||
. ' amount of and/or speed up full index scans. Other than that full index scans can'
|
||||
. ' only be reduced by rewriting queries.'
|
||||
),
|
||||
'justification' => __('Index scans average: %s, this value should be less than 1 per hour'),
|
||||
'justification_formula' => 'ADVISOR_bytime(value,2)',
|
||||
],
|
||||
[
|
||||
'id' => 'Rate of reading fixed position',
|
||||
'name' => __('Rate of reading fixed position'),
|
||||
'formula' => 'Handler_read_rnd / Uptime',
|
||||
'test' => 'value * 60 * 60 > 1',
|
||||
'issue' => __('The rate of reading data from a fixed position is high.'),
|
||||
'recommendation' => __(
|
||||
'This indicates that many queries need to sort results and/or do a full table scan,'
|
||||
. ' including join queries that do not use indexes. Add indexes where applicable.'
|
||||
),
|
||||
'justification' => __('Rate of reading fixed position average: %s, this value should be less than 1 per hour'),
|
||||
'justification_formula' => 'ADVISOR_bytime(value,2)',
|
||||
],
|
||||
[
|
||||
'id' => 'Rate of reading next table row',
|
||||
'name' => __('Rate of reading next table row'),
|
||||
'formula' => 'Handler_read_rnd_next / Uptime',
|
||||
'test' => 'value * 60 * 60 > 1',
|
||||
'issue' => __('The rate of reading the next table row is high.'),
|
||||
'recommendation' => __(
|
||||
'This indicates that many queries are doing full table scans. Add indexes where applicable.'
|
||||
),
|
||||
'justification' => __('Rate of reading next table row: %s, this value should be less than 1 per hour'),
|
||||
'justification_formula' => 'ADVISOR_bytime(value,2)',
|
||||
],
|
||||
// Temp tables
|
||||
[
|
||||
'id' => 'Different tmp_table_size and max_heap_table_size',
|
||||
'name' => __('Different tmp_table_size and max_heap_table_size'),
|
||||
'formula' => 'tmp_table_size - max_heap_table_size',
|
||||
'test' => 'value !=0',
|
||||
'issue' => __('{tmp_table_size} and {max_heap_table_size} are not the same.'),
|
||||
'recommendation' => __(
|
||||
'If you have deliberately changed one of either: The server uses the lower value of either'
|
||||
. ' to determine the maximum size of in-memory tables. So if you wish to increase the'
|
||||
. ' in-memory table limit you will have to increase the other value as well.'
|
||||
),
|
||||
'justification' => __('Current values are tmp_table_size: %s, max_heap_table_size: %s'),
|
||||
'justification_formula' => 'ADVISOR_formatByteDown(tmp_table_size, 2, 2),'
|
||||
. ' ADVISOR_formatByteDown(max_heap_table_size, 2, 2)',
|
||||
],
|
||||
[
|
||||
'id' => 'Percentage of temp tables on disk',
|
||||
'name' => __('Percentage of temp tables on disk'),
|
||||
'precondition' => 'Created_tmp_tables + Created_tmp_disk_tables > 0',
|
||||
'formula' => 'Created_tmp_disk_tables / (Created_tmp_tables + Created_tmp_disk_tables) * 100',
|
||||
'test' => 'value > 25',
|
||||
'issue' => __('Many temporary tables are being written to disk instead of being kept in memory.'),
|
||||
'recommendation' => __(
|
||||
'Increasing {max_heap_table_size} and {tmp_table_size} might help. However some'
|
||||
. ' temporary tables are always being written to disk, independent of the value of these variables.'
|
||||
. ' To eliminate these you will have to rewrite your queries to avoid those conditions'
|
||||
. ' (Within a temporary table: Presence of a BLOB or TEXT column or presence of a column'
|
||||
. ' bigger than 512 bytes) as mentioned in the beginning of an <a href="'
|
||||
. 'https://www.facebook.com/note.php?note_id=10150111255065841&comments'
|
||||
. '">Article by the Pythian Group</a>'
|
||||
),
|
||||
'justification' => __(
|
||||
'%s%% of all temporary tables are being written to disk, this value should be below 25%%'
|
||||
),
|
||||
'justification_formula' => 'round(value,1)',
|
||||
],
|
||||
[
|
||||
'id' => 'Temp disk rate',
|
||||
'name' => __('Temp disk rate'),
|
||||
'precondition' => '!fired(\'Percentage of temp tables on disk\')',
|
||||
'formula' => 'Created_tmp_disk_tables / Uptime',
|
||||
'test' => 'value * 60 * 60 > 1',
|
||||
'issue' => __('Many temporary tables are being written to disk instead of being kept in memory.'),
|
||||
'recommendation' => __(
|
||||
'Increasing {max_heap_table_size} and {tmp_table_size} might help. However some'
|
||||
. ' temporary tables are always being written to disk, independent of the value of these variables.'
|
||||
. ' To eliminate these you will have to rewrite your queries to avoid those conditions'
|
||||
. ' (Within a temporary table: Presence of a BLOB or TEXT column or presence of a column'
|
||||
. ' bigger than 512 bytes) as mentioned in the <a href="'
|
||||
. 'https://dev.mysql.com/doc/refman/8.0/en/internal-temporary-tables.html'
|
||||
. '">MySQL Documentation</a>'
|
||||
),
|
||||
'justification' => __(
|
||||
'Rate of temporary tables being written to disk: %s, this value should be less than 1 per hour'
|
||||
),
|
||||
'justification_formula' => 'ADVISOR_bytime(value,2)',
|
||||
],
|
||||
// MyISAM index cache
|
||||
[
|
||||
'id' => 'MyISAM key buffer size',
|
||||
'name' => __('MyISAM key buffer size'),
|
||||
'formula' => 'key_buffer_size',
|
||||
'test' => 'value == 0',
|
||||
'issue' => __('Key buffer is not initialized. No MyISAM indexes will be cached.'),
|
||||
'recommendation' => __(
|
||||
'Set {key_buffer_size} depending on the size of your MyISAM indexes. 64M is a good start.'
|
||||
),
|
||||
'justification' => __('key_buffer_size is 0'),
|
||||
],
|
||||
[
|
||||
'id' => 'Max % MyISAM key buffer ever used',
|
||||
/* xgettext:no-php-format */
|
||||
'name' => __('Max % MyISAM key buffer ever used'),
|
||||
'precondition' => 'key_buffer_size > 0',
|
||||
'formula' => 'Key_blocks_used * key_cache_block_size / key_buffer_size * 100',
|
||||
'test' => 'value < 95',
|
||||
/* xgettext:no-php-format */
|
||||
'issue' => __('MyISAM key buffer (index cache) % used is low.'),
|
||||
'recommendation' => __(
|
||||
'You may need to decrease the size of {key_buffer_size}, re-examine your tables to see'
|
||||
. ' if indexes have been removed, or examine queries and expectations'
|
||||
. ' about what indexes are being used.'
|
||||
),
|
||||
'justification' => __('max %% MyISAM key buffer ever used: %s%%, this value should be above 95%%'),
|
||||
'justification_formula' => 'round(value,1)',
|
||||
],
|
||||
[
|
||||
'id' => 'Percentage of MyISAM key buffer used',
|
||||
'name' => __('Percentage of MyISAM key buffer used'),
|
||||
// Don't fire if above rule fired - we don't need the same advice twice
|
||||
'precondition' => 'key_buffer_size > 0 && !fired(\'Max % MyISAM key buffer ever used\')',
|
||||
'formula' => '( 1 - Key_blocks_unused * key_cache_block_size / key_buffer_size) * 100',
|
||||
'test' => 'value < 95',
|
||||
/* xgettext:no-php-format */
|
||||
'issue' => __('MyISAM key buffer (index cache) % used is low.'),
|
||||
'recommendation' => __(
|
||||
'You may need to decrease the size of {key_buffer_size}, re-examine your tables to see'
|
||||
. ' if indexes have been removed, or examine queries and expectations'
|
||||
. ' about what indexes are being used.'
|
||||
),
|
||||
'justification' => __('%% MyISAM key buffer used: %s%%, this value should be above 95%%'),
|
||||
'justification_formula' => 'round(value,1)',
|
||||
],
|
||||
[
|
||||
'id' => 'Percentage of index reads from memory',
|
||||
'name' => __('Percentage of index reads from memory'),
|
||||
'precondition' => 'Key_read_requests > 0',
|
||||
'formula' => '100 - (Key_reads / Key_read_requests * 100)',
|
||||
'test' => 'value < 95',
|
||||
/* xgettext:no-php-format */
|
||||
'issue' => __('The % of indexes that use the MyISAM key buffer is low.'),
|
||||
'recommendation' => __('You may need to increase {key_buffer_size}.'),
|
||||
'justification' => __('Index reads from memory: %s%%, this value should be above 95%%'),
|
||||
'justification_formula' => 'round(value,1)',
|
||||
],
|
||||
// Other caches
|
||||
[
|
||||
'id' => 'Rate of table open',
|
||||
'name' => __('Rate of table open'),
|
||||
'formula' => 'Opened_tables / Uptime',
|
||||
'test' => 'value*60*60 > 10',
|
||||
'issue' => __('The rate of opening tables is high.'),
|
||||
'recommendation' => __(
|
||||
'Opening tables requires disk I/O which is costly.'
|
||||
. ' Increasing {table_open_cache} might avoid this.'
|
||||
),
|
||||
'justification' => __('Opened table rate: %s, this value should be less than 10 per hour'),
|
||||
'justification_formula' => 'ADVISOR_bytime(value,2)',
|
||||
],
|
||||
[
|
||||
'id' => 'Percentage of used open files limit',
|
||||
'name' => __('Percentage of used open files limit'),
|
||||
'formula' => 'Open_files / open_files_limit * 100',
|
||||
'test' => 'value > 85',
|
||||
'issue' => __(
|
||||
'The number of open files is approaching the max number of open files.'
|
||||
. ' You may get a "Too many open files" error.'
|
||||
),
|
||||
'recommendation' => __(
|
||||
'Consider increasing {open_files_limit}, and check the error log when'
|
||||
. ' restarting after changing {open_files_limit}.'
|
||||
),
|
||||
'justification' => __('The number of opened files is at %s%% of the limit. It should be below 85%%'),
|
||||
'justification_formula' => 'round(value,1)',
|
||||
],
|
||||
[
|
||||
'id' => 'Rate of open files',
|
||||
'name' => __('Rate of open files'),
|
||||
'formula' => 'Open_files / Uptime',
|
||||
'test' => 'value * 60 * 60 > 5',
|
||||
'issue' => __('The rate of opening files is high.'),
|
||||
'recommendation' => __(
|
||||
'Consider increasing {open_files_limit}, and check the error log when'
|
||||
. ' restarting after changing {open_files_limit}.'
|
||||
),
|
||||
'justification' => __('Opened files rate: %s, this value should be less than 5 per hour'),
|
||||
'justification_formula' => 'ADVISOR_bytime(value,2)',
|
||||
],
|
||||
[
|
||||
'id' => 'Immediate table locks %',
|
||||
/* xgettext:no-php-format */
|
||||
'name' => __('Immediate table locks %'),
|
||||
'precondition' => 'Table_locks_waited + Table_locks_immediate > 0',
|
||||
'formula' => 'Table_locks_immediate / (Table_locks_waited + Table_locks_immediate) * 100',
|
||||
'test' => 'value < 95',
|
||||
'issue' => __('Too many table locks were not granted immediately.'),
|
||||
'recommendation' => __('Optimize queries and/or use InnoDB to reduce lock wait.'),
|
||||
'justification' => __('Immediate table locks: %s%%, this value should be above 95%%'),
|
||||
'justification_formula' => 'round(value,1)',
|
||||
],
|
||||
[
|
||||
'id' => 'Table lock wait rate',
|
||||
'name' => __('Table lock wait rate'),
|
||||
'formula' => 'Table_locks_waited / Uptime',
|
||||
'test' => 'value * 60 * 60 > 1',
|
||||
'issue' => __('Too many table locks were not granted immediately.'),
|
||||
'recommendation' => __('Optimize queries and/or use InnoDB to reduce lock wait.'),
|
||||
'justification' => __('Table lock wait rate: %s, this value should be less than 1 per hour'),
|
||||
'justification_formula' => 'ADVISOR_bytime(value,2)',
|
||||
],
|
||||
[
|
||||
'id' => 'Thread cache',
|
||||
'name' => __('Thread cache'),
|
||||
'formula' => 'thread_cache_size',
|
||||
'test' => 'value < 1',
|
||||
'issue' => __('Thread cache is disabled, resulting in more overhead from new connections to MySQL.'),
|
||||
'recommendation' => __('Enable the thread cache by setting {thread_cache_size} > 0.'),
|
||||
'justification' => __('The thread cache is set to 0'),
|
||||
],
|
||||
[
|
||||
'id' => 'Thread cache hit rate %',
|
||||
/* xgettext:no-php-format */
|
||||
'name' => __('Thread cache hit rate %'),
|
||||
'precondition' => 'thread_cache_size > 0',
|
||||
'formula' => '100 - Threads_created / Connections',
|
||||
'test' => 'value < 80',
|
||||
'issue' => __('Thread cache is not efficient.'),
|
||||
'recommendation' => __('Increase {thread_cache_size}.'),
|
||||
'justification' => __('Thread cache hitrate: %s%%, this value should be above 80%%'),
|
||||
'justification_formula' => 'round(value,1)',
|
||||
],
|
||||
[
|
||||
'id' => 'Threads that are slow to launch',
|
||||
'name' => __('Threads that are slow to launch'),
|
||||
'precondition' => 'slow_launch_time > 0',
|
||||
'formula' => 'Slow_launch_threads',
|
||||
'test' => 'value > 0',
|
||||
'issue' => __('There are too many threads that are slow to launch.'),
|
||||
'recommendation' => __(
|
||||
'This generally happens in case of general system overload as it is pretty simple'
|
||||
. ' operations. You might want to monitor your system load carefully.'
|
||||
),
|
||||
'justification' => __('%s thread(s) took longer than %s seconds to start, it should be 0'),
|
||||
'justification_formula' => 'value, slow_launch_time',
|
||||
],
|
||||
[
|
||||
'id' => 'Slow launch time',
|
||||
'name' => __('Slow launch time'),
|
||||
'formula' => 'slow_launch_time',
|
||||
'test' => 'value > 2',
|
||||
'issue' => __('Slow_launch_time is above 2s.'),
|
||||
'recommendation' => __(
|
||||
'Set {slow_launch_time} to 1s or 2s to correctly count threads that are slow to launch.'
|
||||
),
|
||||
'justification' => __('slow_launch_time is set to %s'),
|
||||
'justification_formula' => 'value',
|
||||
],
|
||||
// Connections
|
||||
[
|
||||
'id' => 'Percentage of used connections',
|
||||
'name' => __('Percentage of used connections'),
|
||||
'formula' => 'Max_used_connections / max_connections * 100',
|
||||
'test' => 'value > 80',
|
||||
'issue' => __(
|
||||
'The maximum amount of used connections is getting close to the value of {max_connections}.'
|
||||
),
|
||||
'recommendation' => __(
|
||||
'Increase {max_connections}, or decrease {wait_timeout} so that connections that do not'
|
||||
. ' close database handlers properly get killed sooner.'
|
||||
. ' Make sure the code closes database handlers properly.'
|
||||
),
|
||||
'justification' => __('Max_used_connections is at %s%% of max_connections, it should be below 80%%'),
|
||||
'justification_formula' => 'round(value,1)',
|
||||
],
|
||||
[
|
||||
'id' => 'Percentage of aborted connections',
|
||||
'name' => __('Percentage of aborted connections'),
|
||||
'formula' => 'Aborted_connects / Connections * 100',
|
||||
'test' => 'value > 1',
|
||||
'issue' => __('Too many connections are aborted.'),
|
||||
'recommendation' => __(
|
||||
'Connections are usually aborted when they cannot be authorized. <a href="'
|
||||
. 'https://www.percona.com/blog/2008/08/23/how-to-track-down-the-source-of-aborted_connects/'
|
||||
. '">This article</a> might help you track down the source.'
|
||||
),
|
||||
'justification' => __('%s%% of all connections are aborted. This value should be below 1%%'),
|
||||
'justification_formula' => 'round(value,1)',
|
||||
],
|
||||
[
|
||||
'id' => 'Rate of aborted connections',
|
||||
'name' => __('Rate of aborted connections'),
|
||||
'formula' => 'Aborted_connects / Uptime',
|
||||
'test' => 'value * 60 * 60 > 1',
|
||||
'issue' => __('Too many connections are aborted.'),
|
||||
'recommendation' => __(
|
||||
'Connections are usually aborted when they cannot be authorized. <a href="'
|
||||
. 'https://www.percona.com/blog/2008/08/23/how-to-track-down-the-source-of-aborted_connects/'
|
||||
. '">This article</a> might help you track down the source.'
|
||||
),
|
||||
'justification' => __('Aborted connections rate is at %s, this value should be less than 1 per hour'),
|
||||
'justification_formula' => 'ADVISOR_bytime(value,2)',
|
||||
],
|
||||
[
|
||||
'id' => 'Percentage of aborted clients',
|
||||
'name' => __('Percentage of aborted clients'),
|
||||
'formula' => 'Aborted_clients / Connections * 100',
|
||||
'test' => 'value > 2',
|
||||
'issue' => __('Too many clients are aborted.'),
|
||||
'recommendation' => __(
|
||||
'Clients are usually aborted when they did not close their connection to MySQL properly.'
|
||||
. ' This can be due to network issues or code not closing a database handler properly.'
|
||||
. ' Check your network and code.'
|
||||
),
|
||||
'justification' => __('%s%% of all clients are aborted. This value should be below 2%%'),
|
||||
'justification_formula' => 'round(value,1)',
|
||||
],
|
||||
[
|
||||
'id' => 'Rate of aborted clients',
|
||||
'name' => __('Rate of aborted clients'),
|
||||
'formula' => 'Aborted_clients / Uptime',
|
||||
'test' => 'value * 60 * 60 > 1',
|
||||
'issue' => __('Too many clients are aborted.'),
|
||||
'recommendation' => __(
|
||||
'Clients are usually aborted when they did not close their connection to MySQL properly.'
|
||||
. ' This can be due to network issues or code not closing a database handler properly.'
|
||||
. ' Check your network and code.'
|
||||
),
|
||||
'justification' => __('Aborted client rate is at %s, this value should be less than 1 per hour'),
|
||||
'justification_formula' => 'ADVISOR_bytime(value,2)',
|
||||
],
|
||||
// InnoDB
|
||||
[
|
||||
'id' => 'Is InnoDB disabled?',
|
||||
'name' => __('Is InnoDB disabled?'),
|
||||
'precondition' => 'PMA_MYSQL_INT_VERSION < 50600',
|
||||
'formula' => 'have_innodb',
|
||||
'test' => 'value != "YES"',
|
||||
'issue' => __('You do not have InnoDB enabled.'),
|
||||
'recommendation' => __('InnoDB is usually the better choice for table engines.'),
|
||||
'justification' => __('have_innodb is set to \'value\''),
|
||||
],
|
||||
[
|
||||
'id' => 'InnoDB log size',
|
||||
'name' => __('InnoDB log size'),
|
||||
'precondition' => 'innodb_buffer_pool_size > 0',
|
||||
'formula' => '(innodb_log_file_size * innodb_log_files_in_group)/ innodb_buffer_pool_size * 100',
|
||||
'test' => 'value < 20 && innodb_log_file_size / (1024 * 1024) < 256',
|
||||
'issue' => __(
|
||||
'The InnoDB log file size is not an appropriate size, in relation to the InnoDB buffer pool.'
|
||||
),
|
||||
'recommendation' => __(/* xgettext:no-php-format */
|
||||
'Especially on a system with a lot of writes to InnoDB tables you should set'
|
||||
. ' {innodb_log_file_size} to 25% of {innodb_buffer_pool_size}. However the bigger this value,'
|
||||
. ' the longer the recovery time will be when database crashes, so this value should not be set'
|
||||
. ' much higher than 256 MiB. Please note however that you cannot simply change the value of'
|
||||
. ' this variable. You need to shutdown the server, remove the InnoDB log files, set the new'
|
||||
. ' value in my.cnf, start the server, then check the error logs if everything went fine.'
|
||||
. ' See also <a href="'
|
||||
. 'https://mysqldatabaseadministration.blogspot.com/2007/01/increase-innodblogfilesize-proper-way.html'
|
||||
. '">this blog entry</a>'
|
||||
),
|
||||
'justification' => __(
|
||||
'Your InnoDB log size is at %s%% in relation to the InnoDB buffer pool size,'
|
||||
. ' it should not be below 20%%'
|
||||
),
|
||||
'justification_formula' => 'round(value,1)',
|
||||
],
|
||||
[
|
||||
'id' => 'Max InnoDB log size',
|
||||
'name' => __('Max InnoDB log size'),
|
||||
'precondition' => 'innodb_buffer_pool_size > 0 && innodb_log_file_size / innodb_buffer_pool_size * 100 < 30',
|
||||
'formula' => 'innodb_log_file_size / (1024 * 1024)',
|
||||
'test' => 'value > 256',
|
||||
'issue' => __('The InnoDB log file size is inadequately large.'),
|
||||
'recommendation' => __(/* xgettext:no-php-format */
|
||||
'It is usually sufficient to set {innodb_log_file_size} to 25% of the size of'
|
||||
. ' {innodb_buffer_pool_size}. A very big {innodb_log_file_size} slows down the recovery'
|
||||
. ' time after a database crash considerably. See also '
|
||||
. '<a href="https://www.percona.com/blog/2006/07/03/choosing-proper-innodb_log_file_size/">'
|
||||
. 'this Article</a>. You need to shutdown the server, remove the InnoDB log files, set the'
|
||||
. ' new value in my.cnf, start the server, then check the error logs'
|
||||
. ' if everything went fine. See also <a href="'
|
||||
. 'https://mysqldatabaseadministration.blogspot.com/2007/01/increase-innodblogfilesize-proper-way.html'
|
||||
. '">this blog entry</a>'
|
||||
),
|
||||
'justification' => __('Your absolute InnoDB log size is %s MiB'),
|
||||
'justification_formula' => 'round(value,1)',
|
||||
],
|
||||
[
|
||||
'id' => 'InnoDB buffer pool size',
|
||||
'name' => __('InnoDB buffer pool size'),
|
||||
'precondition' => 'system_memory > 0',
|
||||
'formula' => 'innodb_buffer_pool_size / system_memory * 100',
|
||||
'test' => 'value < 60',
|
||||
'issue' => __('Your InnoDB buffer pool is fairly small.'),
|
||||
'recommendation' => __(/* xgettext:no-php-format */
|
||||
'The InnoDB buffer pool has a profound impact on performance for InnoDB tables.'
|
||||
. ' Assign all your remaining memory to this buffer. For database servers that use solely InnoDB'
|
||||
. ' as storage engine and have no other services (e.g. a web server) running, you may set this'
|
||||
. ' as high as 80% of your available memory. If that is not the case, you need to carefully'
|
||||
. ' assess the memory consumption of your other services and non-InnoDB-Tables and set this'
|
||||
. ' variable accordingly. If it is set too high, your system will start swapping,'
|
||||
. ' which decreases performance significantly. See also '
|
||||
. '<a href="https://www.percona.com/blog/2007/11/03/choosing-innodb_buffer_pool_size/">this article</a>'
|
||||
),
|
||||
'justification' => __(
|
||||
'You are currently using %s%% of your memory for the InnoDB buffer pool.'
|
||||
. ' This rule fires if you are assigning less than 60%%, however this might be perfectly'
|
||||
. ' adequate for your system if you don\'t have much InnoDB tables'
|
||||
. ' or other services running on the same machine.'
|
||||
),
|
||||
'justification_formula' => 'value',
|
||||
],
|
||||
// Other
|
||||
[
|
||||
'id' => 'MyISAM concurrent inserts',
|
||||
'name' => __('MyISAM concurrent inserts'),
|
||||
'formula' => 'concurrent_insert',
|
||||
'test' => 'value === 0 || value === \'NEVER\'',
|
||||
'issue' => __('Enable {concurrent_insert} by setting it to 1'),
|
||||
'recommendation' => __(
|
||||
'Setting {concurrent_insert} to 1 reduces contention between'
|
||||
. ' readers and writers for a given table. See also '
|
||||
. '<a href="https://dev.mysql.com/doc/refman/5.5/en/concurrent-inserts.html">MySQL Documentation</a>'
|
||||
),
|
||||
'justification' => __('concurrent_insert is set to 0'),
|
||||
],
|
||||
];
|
122
admin/phpMyAdmin/libraries/advisory_rules_mysql_before80003.php
Normal file
122
admin/phpMyAdmin/libraries/advisory_rules_mysql_before80003.php
Normal file
|
@ -0,0 +1,122 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
// Query cache
|
||||
[
|
||||
'id' => 'Query cache disabled',
|
||||
'name' => __('Query cache disabled'),
|
||||
'formula' => 'query_cache_size',
|
||||
'test' => 'value == 0 || query_cache_type == \'OFF\' || query_cache_type == \'0\'',
|
||||
'issue' => __('The query cache is not enabled.'),
|
||||
'recommendation' => __(
|
||||
'The query cache is known to greatly improve performance if configured correctly. Enable it by'
|
||||
. ' setting {query_cache_size} to a 2 digit MiB value and setting {query_cache_type} to \'ON\'.'
|
||||
. ' <b>Note:</b> If you are using memcached, ignore this recommendation.'
|
||||
),
|
||||
'justification' => __('query_cache_size is set to 0 or query_cache_type is set to \'OFF\''),
|
||||
],
|
||||
[
|
||||
'id' => 'Query cache efficiency (%)',
|
||||
/* xgettext:no-php-format */
|
||||
'name' => __('Query cache efficiency (%)'),
|
||||
'precondition' => 'Com_select + Qcache_hits > 0 && !fired(\'Query cache disabled\')',
|
||||
'formula' => 'Qcache_hits / (Com_select + Qcache_hits) * 100',
|
||||
'test' => 'value < 20',
|
||||
'issue' => __('Query cache not running efficiently, it has a low hit rate.'),
|
||||
'recommendation' => __('Consider increasing {query_cache_limit}.'),
|
||||
'justification' => __('The current query cache hit rate of %s%% is below 20%%'),
|
||||
'justification_formula' => 'round(value,1)',
|
||||
],
|
||||
[
|
||||
'id' => 'Query Cache usage',
|
||||
'name' => __('Query Cache usage'),
|
||||
'precondition' => '!fired(\'Query cache disabled\')',
|
||||
'formula' => '100 - Qcache_free_memory / query_cache_size * 100',
|
||||
'test' => 'value < 80',
|
||||
/* xgettext:no-php-format */
|
||||
'issue' => __('Less than 80% of the query cache is being utilized.'),
|
||||
'recommendation' => __(
|
||||
'This might be caused by {query_cache_limit} being too low.'
|
||||
. ' Flushing the query cache might help as well.'
|
||||
),
|
||||
'justification' => __(
|
||||
'The current ratio of free query cache memory to total query'
|
||||
. ' cache size is %s%%. It should be above 80%%'
|
||||
),
|
||||
'justification_formula' => 'round(value,1)',
|
||||
],
|
||||
[
|
||||
'id' => 'Query cache fragmentation',
|
||||
'name' => __('Query cache fragmentation'),
|
||||
'precondition' => '!fired(\'Query cache disabled\')',
|
||||
'formula' => 'Qcache_free_blocks / (Qcache_total_blocks / 2) * 100',
|
||||
'test' => 'value > 20',
|
||||
'issue' => __('The query cache is considerably fragmented.'),
|
||||
'recommendation' => __(
|
||||
'Severe fragmentation is likely to (further) increase Qcache_lowmem_prunes. This might be'
|
||||
. ' caused by many Query cache low memory prunes due to {query_cache_size} being too small. For a'
|
||||
. ' immediate but short lived fix you can flush the query cache (might lock the query cache for a'
|
||||
. ' long time). Carefully adjusting {query_cache_min_res_unit} to a lower value might help too,'
|
||||
. ' e.g. you can set it to the average size of your queries in the cache using this formula:'
|
||||
. ' (query_cache_size - qcache_free_memory) / qcache_queries_in_cache'
|
||||
),
|
||||
'justification' => __(
|
||||
'The cache is currently fragmented by %s%% , with 100%% fragmentation meaning that the query'
|
||||
. ' cache is an alternating pattern of free and used blocks. This value should be below 20%%.'
|
||||
),
|
||||
'justification_formula' => 'round(value,1)',
|
||||
],
|
||||
[
|
||||
'id' => 'Query cache low memory prunes',
|
||||
'name' => __('Query cache low memory prunes'),
|
||||
'precondition' => 'Qcache_inserts > 0 && !fired(\'Query cache disabled\')',
|
||||
'formula' => 'Qcache_lowmem_prunes / Qcache_inserts * 100',
|
||||
'test' => 'value > 0.1',
|
||||
'issue' => __('Cached queries are removed due to low query cache memory from the query cache.'),
|
||||
'recommendation' => __(
|
||||
'You might want to increase {query_cache_size}, however keep in mind that the overhead of'
|
||||
. ' maintaining the cache is likely to increase with its size, so do this in small increments'
|
||||
. ' and monitor the results.'
|
||||
),
|
||||
'justification' => __(
|
||||
'The ratio of removed queries to inserted queries is %s%%. The lower this value is,'
|
||||
. ' the better (This rules firing limit: 0.1%%)'
|
||||
),
|
||||
'justification_formula' => 'round(value,1)',
|
||||
],
|
||||
[
|
||||
'id' => 'Query cache max size',
|
||||
'name' => __('Query cache max size'),
|
||||
'precondition' => '!fired(\'Query cache disabled\')',
|
||||
'formula' => 'query_cache_size',
|
||||
'test' => 'value > 1024 * 1024 * 128',
|
||||
'issue' => __(
|
||||
'The query cache size is above 128 MiB. Big query caches may cause significant'
|
||||
. ' overhead that is required to maintain the cache.'
|
||||
),
|
||||
'recommendation' => __(
|
||||
'Depending on your environment, it might be performance increasing to reduce this value.'
|
||||
),
|
||||
'justification' => __('Current query cache size: %s'),
|
||||
'justification_formula' => 'ADVISOR_formatByteDown(value, 2, 2)',
|
||||
],
|
||||
[
|
||||
'id' => 'Query cache min result size',
|
||||
'name' => __('Query cache min result size'),
|
||||
'precondition' => '!fired(\'Query cache disabled\')',
|
||||
'formula' => 'query_cache_limit',
|
||||
'test' => 'value == 1024*1024',
|
||||
'issue' => __('The max size of the result set in the query cache is the default of 1 MiB.'),
|
||||
'recommendation' => __(
|
||||
'Changing {query_cache_limit} (usually by increasing) may increase efficiency. This variable'
|
||||
. ' determines the maximum size a query result may have to be inserted into the query cache.'
|
||||
. ' If there are many query results above 1 MiB that are well cacheable (many reads, little writes)'
|
||||
. ' then increasing {query_cache_limit} will increase efficiency. Whereas in the case of many query'
|
||||
. ' results being above 1 MiB that are not very well cacheable (often invalidated due to table'
|
||||
. ' updates) increasing {query_cache_limit} might reduce efficiency.'
|
||||
),
|
||||
'justification' => __('query_cache_limit is set to 1 MiB'),
|
||||
],
|
||||
];
|
1335
admin/phpMyAdmin/libraries/cache/routes.cache.php
vendored
Normal file
1335
admin/phpMyAdmin/libraries/cache/routes.cache.php
vendored
Normal file
File diff suppressed because it is too large
Load diff
20
admin/phpMyAdmin/libraries/certs/12d55845.0
Normal file
20
admin/phpMyAdmin/libraries/certs/12d55845.0
Normal file
|
@ -0,0 +1,20 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/
|
||||
MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
|
||||
DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow
|
||||
PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD
|
||||
Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
|
||||
AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O
|
||||
rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq
|
||||
OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b
|
||||
xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw
|
||||
7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD
|
||||
aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV
|
||||
HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG
|
||||
SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69
|
||||
ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr
|
||||
AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz
|
||||
R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5
|
||||
JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo
|
||||
Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ
|
||||
-----END CERTIFICATE-----
|
20
admin/phpMyAdmin/libraries/certs/2e5ac55d.0
Normal file
20
admin/phpMyAdmin/libraries/certs/2e5ac55d.0
Normal file
|
@ -0,0 +1,20 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/
|
||||
MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
|
||||
DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow
|
||||
PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD
|
||||
Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
|
||||
AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O
|
||||
rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq
|
||||
OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b
|
||||
xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw
|
||||
7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD
|
||||
aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV
|
||||
HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG
|
||||
SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69
|
||||
ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr
|
||||
AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz
|
||||
R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5
|
||||
JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo
|
||||
Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ
|
||||
-----END CERTIFICATE-----
|
31
admin/phpMyAdmin/libraries/certs/4042bcee.0
Normal file
31
admin/phpMyAdmin/libraries/certs/4042bcee.0
Normal file
|
@ -0,0 +1,31 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
|
||||
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
|
||||
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
|
||||
WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
|
||||
ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
|
||||
MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
|
||||
h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
|
||||
0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
|
||||
A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
|
||||
T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
|
||||
B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
|
||||
B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
|
||||
KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
|
||||
OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
|
||||
jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
|
||||
qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
|
||||
rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
|
||||
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
|
||||
hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
|
||||
ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
|
||||
3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
|
||||
NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
|
||||
ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
|
||||
TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
|
||||
jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
|
||||
oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
|
||||
4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
|
||||
mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
|
||||
emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
|
||||
-----END CERTIFICATE-----
|
31
admin/phpMyAdmin/libraries/certs/6187b673.0
Normal file
31
admin/phpMyAdmin/libraries/certs/6187b673.0
Normal file
|
@ -0,0 +1,31 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
|
||||
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
|
||||
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
|
||||
WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
|
||||
ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
|
||||
MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
|
||||
h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
|
||||
0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
|
||||
A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
|
||||
T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
|
||||
B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
|
||||
B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
|
||||
KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
|
||||
OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
|
||||
jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
|
||||
qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
|
||||
rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
|
||||
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
|
||||
hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
|
||||
ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
|
||||
3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
|
||||
NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
|
||||
ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
|
||||
TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
|
||||
jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
|
||||
oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
|
||||
4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
|
||||
mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
|
||||
emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
|
||||
-----END CERTIFICATE-----
|
16
admin/phpMyAdmin/libraries/certs/README.rst
Normal file
16
admin/phpMyAdmin/libraries/certs/README.rst
Normal file
|
@ -0,0 +1,16 @@
|
|||
phpMyAdmin SSL certificates
|
||||
===========================
|
||||
|
||||
This directory contains copy of root certificates used to sign phpmyadmin.net
|
||||
and reports.phpmyadmin.net websites. It is used to allow operation on systems
|
||||
where the certificates are missing or wrongly configured (happens on Windows
|
||||
with wrongly compiled CURL).
|
||||
|
||||
Currently included SSL certificates:
|
||||
|
||||
* ISRG Root X1
|
||||
* DST Root CA X3
|
||||
|
||||
See https://letsencrypt.org/certificates/ for more info on them.
|
||||
|
||||
In case of update, the filenames can be generated using c_rehash tool.
|
51
admin/phpMyAdmin/libraries/certs/cacert.pem
Normal file
51
admin/phpMyAdmin/libraries/certs/cacert.pem
Normal file
|
@ -0,0 +1,51 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
|
||||
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
|
||||
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
|
||||
WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
|
||||
ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
|
||||
MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
|
||||
h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
|
||||
0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
|
||||
A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
|
||||
T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
|
||||
B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
|
||||
B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
|
||||
KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
|
||||
OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
|
||||
jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
|
||||
qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
|
||||
rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
|
||||
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
|
||||
hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
|
||||
ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
|
||||
3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
|
||||
NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
|
||||
ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
|
||||
TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
|
||||
jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
|
||||
oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
|
||||
4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
|
||||
mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
|
||||
emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/
|
||||
MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
|
||||
DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow
|
||||
PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD
|
||||
Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
|
||||
AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O
|
||||
rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq
|
||||
OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b
|
||||
xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw
|
||||
7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD
|
||||
aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV
|
||||
HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG
|
||||
SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69
|
||||
ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr
|
||||
AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz
|
||||
R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5
|
||||
JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo
|
||||
Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ
|
||||
-----END CERTIFICATE-----
|
431
admin/phpMyAdmin/libraries/classes/Advisor.php
Normal file
431
admin/phpMyAdmin/libraries/classes/Advisor.php
Normal file
|
@ -0,0 +1,431 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin;
|
||||
|
||||
use PhpMyAdmin\Server\SysInfo\SysInfo;
|
||||
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
|
||||
use Throwable;
|
||||
use function array_merge;
|
||||
use function htmlspecialchars;
|
||||
use function implode;
|
||||
use function pow;
|
||||
use function preg_match;
|
||||
use function preg_replace_callback;
|
||||
use function round;
|
||||
use function sprintf;
|
||||
use function strpos;
|
||||
use function substr;
|
||||
use function vsprintf;
|
||||
|
||||
/**
|
||||
* A simple rules engine, that executes the rules in the advisory_rules files.
|
||||
*/
|
||||
class Advisor
|
||||
{
|
||||
private const GENERIC_RULES_FILE = 'libraries/advisory_rules_generic.php';
|
||||
private const BEFORE_MYSQL80003_RULES_FILE = 'libraries/advisory_rules_mysql_before80003.php';
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/** @var array */
|
||||
private $variables;
|
||||
|
||||
/** @var array */
|
||||
private $globals;
|
||||
|
||||
/** @var array */
|
||||
private $rules;
|
||||
|
||||
/** @var array */
|
||||
private $runResult;
|
||||
|
||||
/** @var ExpressionLanguage */
|
||||
private $expression;
|
||||
|
||||
/**
|
||||
* @param DatabaseInterface $dbi DatabaseInterface object
|
||||
* @param ExpressionLanguage $expression ExpressionLanguage object
|
||||
*/
|
||||
public function __construct(DatabaseInterface $dbi, ExpressionLanguage $expression)
|
||||
{
|
||||
$this->dbi = $dbi;
|
||||
$this->expression = $expression;
|
||||
/*
|
||||
* Register functions for ExpressionLanguage, we intentionally
|
||||
* do not implement support for compile as we do not use it.
|
||||
*/
|
||||
$this->expression->register(
|
||||
'round',
|
||||
static function () {
|
||||
},
|
||||
/**
|
||||
* @param array $arguments
|
||||
* @param float $num
|
||||
*/
|
||||
static function ($arguments, $num) {
|
||||
return round($num);
|
||||
}
|
||||
);
|
||||
$this->expression->register(
|
||||
'substr',
|
||||
static function () {
|
||||
},
|
||||
/**
|
||||
* @param array $arguments
|
||||
* @param string $string
|
||||
* @param int $start
|
||||
* @param int $length
|
||||
*/
|
||||
static function ($arguments, $string, $start, $length) {
|
||||
return substr($string, $start, $length);
|
||||
}
|
||||
);
|
||||
$this->expression->register(
|
||||
'preg_match',
|
||||
static function () {
|
||||
},
|
||||
/**
|
||||
* @param array $arguments
|
||||
* @param string $pattern
|
||||
* @param string $subject
|
||||
*/
|
||||
static function ($arguments, $pattern, $subject) {
|
||||
return preg_match($pattern, $subject);
|
||||
}
|
||||
);
|
||||
$this->expression->register(
|
||||
'ADVISOR_bytime',
|
||||
static function () {
|
||||
},
|
||||
/**
|
||||
* @param array $arguments
|
||||
* @param float $num
|
||||
* @param int $precision
|
||||
*/
|
||||
static function ($arguments, $num, $precision) {
|
||||
return self::byTime($num, $precision);
|
||||
}
|
||||
);
|
||||
$this->expression->register(
|
||||
'ADVISOR_timespanFormat',
|
||||
static function () {
|
||||
},
|
||||
/**
|
||||
* @param array $arguments
|
||||
* @param string $seconds
|
||||
*/
|
||||
static function ($arguments, $seconds) {
|
||||
return Util::timespanFormat((int) $seconds);
|
||||
}
|
||||
);
|
||||
$this->expression->register(
|
||||
'ADVISOR_formatByteDown',
|
||||
static function () {
|
||||
},
|
||||
/**
|
||||
* @param array $arguments
|
||||
* @param int $value
|
||||
* @param int $limes
|
||||
* @param int $comma
|
||||
*/
|
||||
static function ($arguments, $value, $limes = 6, $comma = 0) {
|
||||
return implode(' ', (array) Util::formatByteDown($value, $limes, $comma));
|
||||
}
|
||||
);
|
||||
$this->expression->register(
|
||||
'fired',
|
||||
static function () {
|
||||
},
|
||||
/**
|
||||
* @param array $arguments
|
||||
* @param int $value
|
||||
*/
|
||||
function ($arguments, $value) {
|
||||
if (! isset($this->runResult['fired'])) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Did matching rule fire?
|
||||
foreach ($this->runResult['fired'] as $rule) {
|
||||
if ($rule['id'] == $value) {
|
||||
return '1';
|
||||
}
|
||||
}
|
||||
|
||||
return '0';
|
||||
}
|
||||
);
|
||||
/* Some global variables for advisor */
|
||||
$this->globals = [
|
||||
'PMA_MYSQL_INT_VERSION' => $this->dbi->getVersion(),
|
||||
];
|
||||
}
|
||||
|
||||
private function setVariables(): void
|
||||
{
|
||||
$globalStatus = $this->dbi->fetchResult('SHOW GLOBAL STATUS', 0, 1);
|
||||
$globalVariables = $this->dbi->fetchResult('SHOW GLOBAL VARIABLES', 0, 1);
|
||||
|
||||
$sysInfo = SysInfo::get();
|
||||
$memory = $sysInfo->memory();
|
||||
$systemMemory = ['system_memory' => $memory['MemTotal'] ?? 0];
|
||||
|
||||
$this->variables = array_merge($globalStatus, $globalVariables, $systemMemory);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|int $variable Variable to set
|
||||
* @param mixed $value Value to set
|
||||
*/
|
||||
public function setVariable($variable, $value): void
|
||||
{
|
||||
$this->variables[$variable] = $value;
|
||||
}
|
||||
|
||||
private function setRules(): void
|
||||
{
|
||||
$isMariaDB = strpos($this->variables['version'], 'MariaDB') !== false;
|
||||
$genericRules = include ROOT_PATH . self::GENERIC_RULES_FILE;
|
||||
|
||||
if (! $isMariaDB && $this->globals['PMA_MYSQL_INT_VERSION'] >= 80003) {
|
||||
$this->rules = $genericRules;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$extraRules = include ROOT_PATH . self::BEFORE_MYSQL80003_RULES_FILE;
|
||||
$this->rules = array_merge($genericRules, $extraRules);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getRunResult(): array
|
||||
{
|
||||
return $this->runResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function run(): array
|
||||
{
|
||||
$this->setVariables();
|
||||
$this->setRules();
|
||||
$this->runRules();
|
||||
|
||||
return $this->runResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores current error in run results.
|
||||
*
|
||||
* @param string $description description of an error.
|
||||
* @param Throwable $exception exception raised
|
||||
*/
|
||||
private function storeError(string $description, Throwable $exception): void
|
||||
{
|
||||
$this->runResult['errors'][] = $description . ' ' . sprintf(
|
||||
__('Error when evaluating: %s'),
|
||||
$exception->getMessage()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes advisor rules
|
||||
*/
|
||||
private function runRules(): void
|
||||
{
|
||||
$this->runResult = [
|
||||
'fired' => [],
|
||||
'notfired' => [],
|
||||
'unchecked' => [],
|
||||
'errors' => [],
|
||||
];
|
||||
|
||||
foreach ($this->rules as $rule) {
|
||||
$this->variables['value'] = 0;
|
||||
$precondition = true;
|
||||
|
||||
if (isset($rule['precondition'])) {
|
||||
try {
|
||||
$precondition = $this->evaluateRuleExpression($rule['precondition']);
|
||||
} catch (Throwable $e) {
|
||||
$this->storeError(
|
||||
sprintf(
|
||||
__('Failed evaluating precondition for rule \'%s\'.'),
|
||||
$rule['name']
|
||||
),
|
||||
$e
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (! $precondition) {
|
||||
$this->addRule('unchecked', $rule);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$value = $this->evaluateRuleExpression($rule['formula']);
|
||||
} catch (Throwable $e) {
|
||||
$this->storeError(
|
||||
sprintf(
|
||||
__('Failed calculating value for rule \'%s\'.'),
|
||||
$rule['name']
|
||||
),
|
||||
$e
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->variables['value'] = $value;
|
||||
|
||||
try {
|
||||
if ($this->evaluateRuleExpression($rule['test'])) {
|
||||
$this->addRule('fired', $rule);
|
||||
} else {
|
||||
$this->addRule('notfired', $rule);
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
$this->storeError(
|
||||
sprintf(
|
||||
__('Failed running test for rule \'%s\'.'),
|
||||
$rule['name']
|
||||
),
|
||||
$e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a rule to the result list
|
||||
*
|
||||
* @param string $type type of rule
|
||||
* @param array $rule rule itself
|
||||
*/
|
||||
public function addRule(string $type, array $rule): void
|
||||
{
|
||||
if ($type !== 'notfired' && $type !== 'fired') {
|
||||
$this->runResult[$type][] = $rule;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($rule['justification_formula'])) {
|
||||
try {
|
||||
$params = $this->evaluateRuleExpression('[' . $rule['justification_formula'] . ']');
|
||||
} catch (Throwable $e) {
|
||||
$this->storeError(
|
||||
sprintf(__('Failed formatting string for rule \'%s\'.'), $rule['name']),
|
||||
$e
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$rule['justification'] = vsprintf($rule['justification'], $params);
|
||||
}
|
||||
|
||||
// Replaces {server_variable} with 'server_variable'
|
||||
// linking to /server/variables
|
||||
$rule['recommendation'] = preg_replace_callback(
|
||||
'/\{([a-z_0-9]+)\}/Ui',
|
||||
function (array $matches) {
|
||||
return $this->replaceVariable($matches);
|
||||
},
|
||||
$rule['recommendation']
|
||||
);
|
||||
$rule['issue'] = preg_replace_callback(
|
||||
'/\{([a-z_0-9]+)\}/Ui',
|
||||
function (array $matches) {
|
||||
return $this->replaceVariable($matches);
|
||||
},
|
||||
$rule['issue']
|
||||
);
|
||||
|
||||
// Replaces external Links with Core::linkURL() generated links
|
||||
$rule['recommendation'] = preg_replace_callback(
|
||||
'#href=("|\')(https?://[^"\']+)\1#i',
|
||||
function (array $matches) {
|
||||
return $this->replaceLinkURL($matches);
|
||||
},
|
||||
$rule['recommendation']
|
||||
);
|
||||
|
||||
$this->runResult[$type][] = $rule;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for wrapping links with Core::linkURL
|
||||
*
|
||||
* @param array $matches List of matched elements form preg_replace_callback
|
||||
*
|
||||
* @return string Replacement value
|
||||
*/
|
||||
private function replaceLinkURL(array $matches): string
|
||||
{
|
||||
return 'href="' . Core::linkURL($matches[2]) . '" target="_blank" rel="noopener noreferrer"';
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for wrapping variable edit links
|
||||
*
|
||||
* @param array $matches List of matched elements form preg_replace_callback
|
||||
*
|
||||
* @return string Replacement value
|
||||
*/
|
||||
private function replaceVariable(array $matches): string
|
||||
{
|
||||
return '<a href="' . Url::getFromRoute('/server/variables', ['filter' => $matches[1]])
|
||||
. '">' . htmlspecialchars($matches[1]) . '</a>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a code expression, replacing variable names with their respective values
|
||||
*
|
||||
* @return mixed result of evaluated expression
|
||||
*/
|
||||
private function evaluateRuleExpression(string $expression)
|
||||
{
|
||||
return $this->expression->evaluate($expression, array_merge($this->variables, $this->globals));
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats interval like 10 per hour
|
||||
*
|
||||
* @param float $num number to format
|
||||
* @param int $precision required precision
|
||||
*
|
||||
* @return string formatted string
|
||||
*/
|
||||
public static function byTime(float $num, int $precision): string
|
||||
{
|
||||
if ($num >= 1) { // per second
|
||||
$per = __('per second');
|
||||
} elseif ($num * 60 >= 1) { // per minute
|
||||
$num *= 60;
|
||||
$per = __('per minute');
|
||||
} elseif ($num * 60 * 60 >= 1) { // per hour
|
||||
$num *= 60 * 60;
|
||||
$per = __('per hour');
|
||||
} else {
|
||||
$num *= 24 * 60 * 60;
|
||||
$per = __('per day');
|
||||
}
|
||||
|
||||
$num = round($num, $precision);
|
||||
|
||||
if ($num == 0) {
|
||||
$num = '<' . pow(10, -$precision);
|
||||
}
|
||||
|
||||
return $num . ' ' . $per;
|
||||
}
|
||||
}
|
396
admin/phpMyAdmin/libraries/classes/Bookmark.php
Normal file
396
admin/phpMyAdmin/libraries/classes/Bookmark.php
Normal file
|
@ -0,0 +1,396 @@
|
|||
<?php
|
||||
/**
|
||||
* Handles bookmarking SQL queries
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin;
|
||||
|
||||
use const PREG_SET_ORDER;
|
||||
use function count;
|
||||
use function is_array;
|
||||
use function preg_match_all;
|
||||
use function preg_replace;
|
||||
use function str_replace;
|
||||
use function strlen;
|
||||
|
||||
/**
|
||||
* Handles bookmarking SQL queries
|
||||
*/
|
||||
class Bookmark
|
||||
{
|
||||
/**
|
||||
* ID of the bookmark
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $id;
|
||||
/**
|
||||
* Database the bookmark belongs to
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $database;
|
||||
/**
|
||||
* The user to whom the bookmark belongs, empty for public bookmarks
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $currentUser;
|
||||
/**
|
||||
* Label of the bookmark
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $label;
|
||||
/**
|
||||
* SQL query that is bookmarked
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $query;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* Current user
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $user;
|
||||
|
||||
/**
|
||||
* @param DatabaseInterface $dbi DatabaseInterface object
|
||||
* @param string $user Current user
|
||||
*/
|
||||
public function __construct(DatabaseInterface $dbi, string $user)
|
||||
{
|
||||
$this->dbi = $dbi;
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ID of the bookmark
|
||||
*/
|
||||
public function getId(): int
|
||||
{
|
||||
return (int) $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the database of the bookmark
|
||||
*/
|
||||
public function getDatabase(): string
|
||||
{
|
||||
return $this->database;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the user whom the bookmark belongs to
|
||||
*/
|
||||
public function getUser(): string
|
||||
{
|
||||
return $this->currentUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the label of the bookmark
|
||||
*/
|
||||
public function getLabel(): string
|
||||
{
|
||||
return $this->label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the query
|
||||
*/
|
||||
public function getQuery(): string
|
||||
{
|
||||
return $this->query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a bookmark
|
||||
*
|
||||
* @return bool whether the INSERT succeeds or not
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function save(): bool
|
||||
{
|
||||
$cfgBookmark = self::getParams($this->user);
|
||||
if (! is_array($cfgBookmark)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$query = 'INSERT INTO ' . Util::backquote($cfgBookmark['db'])
|
||||
. '.' . Util::backquote($cfgBookmark['table'])
|
||||
. ' (id, dbase, user, query, label) VALUES (NULL, '
|
||||
. "'" . $this->dbi->escapeString($this->database) . "', "
|
||||
. "'" . $this->dbi->escapeString($this->currentUser) . "', "
|
||||
. "'" . $this->dbi->escapeString($this->query) . "', "
|
||||
. "'" . $this->dbi->escapeString($this->label) . "')";
|
||||
|
||||
return $this->dbi->query($query, DatabaseInterface::CONNECT_CONTROL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a bookmark
|
||||
*
|
||||
* @return bool true if successful
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function delete(): bool
|
||||
{
|
||||
$cfgBookmark = self::getParams($this->user);
|
||||
if (! is_array($cfgBookmark)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$query = 'DELETE FROM ' . Util::backquote($cfgBookmark['db'])
|
||||
. '.' . Util::backquote($cfgBookmark['table'])
|
||||
. ' WHERE id = ' . $this->id;
|
||||
|
||||
return $this->dbi->tryQuery($query, DatabaseInterface::CONNECT_CONTROL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of variables in a bookmark
|
||||
*
|
||||
* @return int number of variables
|
||||
*/
|
||||
public function getVariableCount(): int
|
||||
{
|
||||
$matches = [];
|
||||
preg_match_all('/\[VARIABLE[0-9]*\]/', $this->query, $matches, PREG_SET_ORDER);
|
||||
|
||||
return count($matches);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the placeholders in the bookmark query with variables
|
||||
*
|
||||
* @param array $variables array of variables
|
||||
*
|
||||
* @return string query with variables applied
|
||||
*/
|
||||
public function applyVariables(array $variables): string
|
||||
{
|
||||
// remove comments that encloses a variable placeholder
|
||||
$query = (string) preg_replace(
|
||||
'|/\*(.*\[VARIABLE[0-9]*\].*)\*/|imsU',
|
||||
'${1}',
|
||||
$this->query
|
||||
);
|
||||
// replace variable placeholders with values
|
||||
$number_of_variables = $this->getVariableCount();
|
||||
for ($i = 1; $i <= $number_of_variables; $i++) {
|
||||
$var = '';
|
||||
if (! empty($variables[$i])) {
|
||||
$var = $this->dbi->escapeString($variables[$i]);
|
||||
}
|
||||
$query = str_replace('[VARIABLE' . $i . ']', $var, $query);
|
||||
// backward compatibility
|
||||
if ($i != 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$query = str_replace('[VARIABLE]', $var, $query);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the bookmark parameters for the current user
|
||||
*
|
||||
* @param string $user Current user
|
||||
*
|
||||
* @return array|bool the bookmark parameters for the current user
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public static function getParams(string $user)
|
||||
{
|
||||
global $dbi;
|
||||
|
||||
static $cfgBookmark = null;
|
||||
|
||||
if ($cfgBookmark !== null) {
|
||||
return $cfgBookmark;
|
||||
}
|
||||
|
||||
$relation = new Relation($dbi);
|
||||
$cfgRelation = $relation->getRelationsParam();
|
||||
if ($cfgRelation['bookmarkwork']) {
|
||||
$cfgBookmark = [
|
||||
'user' => $user,
|
||||
'db' => $cfgRelation['db'],
|
||||
'table' => $cfgRelation['bookmark'],
|
||||
];
|
||||
} else {
|
||||
$cfgBookmark = false;
|
||||
}
|
||||
|
||||
return $cfgBookmark;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Bookmark object from the parameters
|
||||
*
|
||||
* @param DatabaseInterface $dbi DatabaseInterface object
|
||||
* @param string $user Current user
|
||||
* @param array $bkm_fields the properties of the bookmark to add; here,
|
||||
* $bkm_fields['bkm_sql_query'] is urlencoded
|
||||
* @param bool $all_users whether to make the bookmark
|
||||
* available for all users
|
||||
*
|
||||
* @return Bookmark|false
|
||||
*/
|
||||
public static function createBookmark(
|
||||
DatabaseInterface $dbi,
|
||||
string $user,
|
||||
array $bkm_fields,
|
||||
bool $all_users = false
|
||||
) {
|
||||
if (! (isset($bkm_fields['bkm_sql_query'], $bkm_fields['bkm_label'])
|
||||
&& strlen($bkm_fields['bkm_sql_query']) > 0
|
||||
&& strlen($bkm_fields['bkm_label']) > 0)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$bookmark = new Bookmark($dbi, $user);
|
||||
$bookmark->database = $bkm_fields['bkm_database'];
|
||||
$bookmark->label = $bkm_fields['bkm_label'];
|
||||
$bookmark->query = $bkm_fields['bkm_sql_query'];
|
||||
$bookmark->currentUser = $all_users ? '' : $bkm_fields['bkm_user'];
|
||||
|
||||
return $bookmark;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DatabaseInterface $dbi DatabaseInterface object
|
||||
* @param string $user Current user
|
||||
* @param array $row Resource used to build the bookmark
|
||||
*/
|
||||
protected static function createFromRow(
|
||||
DatabaseInterface $dbi,
|
||||
string $user,
|
||||
$row
|
||||
): Bookmark {
|
||||
$bookmark = new Bookmark($dbi, $user);
|
||||
$bookmark->id = $row['id'];
|
||||
$bookmark->database = $row['dbase'];
|
||||
$bookmark->currentUser = $row['user'];
|
||||
$bookmark->label = $row['label'];
|
||||
$bookmark->query = $row['query'];
|
||||
|
||||
return $bookmark;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of bookmarks defined for the current database
|
||||
*
|
||||
* @param DatabaseInterface $dbi DatabaseInterface object
|
||||
* @param string $user Current user
|
||||
* @param string|false $db the current database name or false
|
||||
*
|
||||
* @return Bookmark[] the bookmarks list
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public static function getList(
|
||||
DatabaseInterface $dbi,
|
||||
string $user,
|
||||
$db = false
|
||||
): array {
|
||||
$cfgBookmark = self::getParams($user);
|
||||
if (! is_array($cfgBookmark)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$query = 'SELECT * FROM ' . Util::backquote($cfgBookmark['db'])
|
||||
. '.' . Util::backquote($cfgBookmark['table'])
|
||||
. " WHERE ( `user` = ''"
|
||||
. " OR `user` = '" . $dbi->escapeString($cfgBookmark['user']) . "' )";
|
||||
if ($db !== false) {
|
||||
$query .= " AND dbase = '" . $dbi->escapeString($db) . "'";
|
||||
}
|
||||
$query .= ' ORDER BY label ASC';
|
||||
|
||||
$result = $dbi->fetchResult(
|
||||
$query,
|
||||
null,
|
||||
null,
|
||||
DatabaseInterface::CONNECT_CONTROL,
|
||||
DatabaseInterface::QUERY_STORE
|
||||
);
|
||||
|
||||
if (! empty($result)) {
|
||||
$bookmarks = [];
|
||||
foreach ($result as $row) {
|
||||
$bookmarks[] = self::createFromRow($dbi, $user, $row);
|
||||
}
|
||||
|
||||
return $bookmarks;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a specific bookmark
|
||||
*
|
||||
* @param DatabaseInterface $dbi DatabaseInterface object
|
||||
* @param string $user Current user
|
||||
* @param string $db the current database name
|
||||
* @param int|string $id an identifier of the bookmark to get
|
||||
* @param string $id_field which field to look up the identifier
|
||||
* @param bool $action_bookmark_all true: get all bookmarks regardless
|
||||
* of the owning user
|
||||
* @param bool $exact_user_match whether to ignore bookmarks with no user
|
||||
*
|
||||
* @return Bookmark|null the bookmark
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public static function get(
|
||||
DatabaseInterface $dbi,
|
||||
string $user,
|
||||
string $db,
|
||||
$id,
|
||||
string $id_field = 'id',
|
||||
bool $action_bookmark_all = false,
|
||||
bool $exact_user_match = false
|
||||
): ?self {
|
||||
$cfgBookmark = self::getParams($user);
|
||||
if (! is_array($cfgBookmark)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$query = 'SELECT * FROM ' . Util::backquote($cfgBookmark['db'])
|
||||
. '.' . Util::backquote($cfgBookmark['table'])
|
||||
. " WHERE dbase = '" . $dbi->escapeString($db) . "'";
|
||||
if (! $action_bookmark_all) {
|
||||
$query .= " AND (user = '"
|
||||
. $dbi->escapeString($cfgBookmark['user']) . "'";
|
||||
if (! $exact_user_match) {
|
||||
$query .= " OR user = ''";
|
||||
}
|
||||
$query .= ')';
|
||||
}
|
||||
$query .= ' AND ' . Util::backquote($id_field)
|
||||
. " = '" . $dbi->escapeString((string) $id) . "' LIMIT 1";
|
||||
|
||||
$result = $dbi->fetchSingleRow($query, 'ASSOC', DatabaseInterface::CONNECT_CONTROL);
|
||||
if (! empty($result)) {
|
||||
return self::createFromRow($dbi, $user, $result);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
352
admin/phpMyAdmin/libraries/classes/BrowseForeigners.php
Normal file
352
admin/phpMyAdmin/libraries/classes/BrowseForeigners.php
Normal file
|
@ -0,0 +1,352 @@
|
|||
<?php
|
||||
/**
|
||||
* Contains functions used by browse foreigners
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin;
|
||||
|
||||
use function asort;
|
||||
use function ceil;
|
||||
use function floor;
|
||||
use function htmlspecialchars;
|
||||
use function is_array;
|
||||
use function mb_strlen;
|
||||
use function mb_substr;
|
||||
|
||||
/**
|
||||
* PhpMyAdmin\BrowseForeigners class
|
||||
*/
|
||||
class BrowseForeigners
|
||||
{
|
||||
/** @var int */
|
||||
private $limitChars;
|
||||
/** @var int */
|
||||
private $maxRows;
|
||||
/** @var int */
|
||||
private $repeatCells;
|
||||
/** @var bool */
|
||||
private $showAll;
|
||||
/** @var string */
|
||||
private $themeImage;
|
||||
|
||||
/** @var Template */
|
||||
public $template;
|
||||
|
||||
/**
|
||||
* @param Template $template Template object
|
||||
*/
|
||||
public function __construct(Template $template)
|
||||
{
|
||||
global $cfg, $PMA_Theme;
|
||||
|
||||
$this->template = $template;
|
||||
|
||||
$this->limitChars = (int) $cfg['LimitChars'];
|
||||
$this->maxRows = (int) $cfg['MaxRows'];
|
||||
$this->repeatCells = (int) $cfg['RepeatCells'];
|
||||
$this->showAll = (bool) $cfg['ShowAll'];
|
||||
$this->themeImage = $PMA_Theme->getImgPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to get html for one relational key
|
||||
*
|
||||
* @param int $horizontal_count the current horizontal count
|
||||
* @param string $header table header
|
||||
* @param array $keys all the keys
|
||||
* @param int $indexByKeyname index by keyname
|
||||
* @param array $descriptions descriptions
|
||||
* @param int $indexByDescription index by description
|
||||
* @param string $current_value current value on the edit form
|
||||
*
|
||||
* @return array the generated html
|
||||
*/
|
||||
private function getHtmlForOneKey(
|
||||
int $horizontal_count,
|
||||
string $header,
|
||||
array $keys,
|
||||
int $indexByKeyname,
|
||||
array $descriptions,
|
||||
int $indexByDescription,
|
||||
string $current_value
|
||||
): array {
|
||||
$horizontal_count++;
|
||||
$output = '';
|
||||
|
||||
// whether the key name corresponds to the selected value in the form
|
||||
$rightKeynameIsSelected = false;
|
||||
$leftKeynameIsSelected = false;
|
||||
|
||||
if ($this->repeatCells > 0 && $horizontal_count > $this->repeatCells) {
|
||||
$output .= $header;
|
||||
$horizontal_count = 0;
|
||||
}
|
||||
|
||||
// key names and descriptions for the left section,
|
||||
// sorted by key names
|
||||
$leftKeyname = $keys[$indexByKeyname];
|
||||
[
|
||||
$leftDescription,
|
||||
$leftDescriptionTitle,
|
||||
] = $this->getDescriptionAndTitle($descriptions[$indexByKeyname]);
|
||||
|
||||
// key names and descriptions for the right section,
|
||||
// sorted by descriptions
|
||||
$rightKeyname = $keys[$indexByDescription];
|
||||
[
|
||||
$rightDescription,
|
||||
$rightDescriptionTitle,
|
||||
] = $this->getDescriptionAndTitle($descriptions[$indexByDescription]);
|
||||
|
||||
$indexByDescription++;
|
||||
|
||||
if (! empty($current_value)) {
|
||||
$rightKeynameIsSelected = $rightKeyname == $current_value;
|
||||
$leftKeynameIsSelected = $leftKeyname == $current_value;
|
||||
}
|
||||
|
||||
$output .= '<tr class="noclick">';
|
||||
|
||||
$output .= $this->template->render('table/browse_foreigners/column_element', [
|
||||
'keyname' => $leftKeyname,
|
||||
'description' => $leftDescription,
|
||||
'title' => $leftDescriptionTitle,
|
||||
'is_selected' => $leftKeynameIsSelected,
|
||||
'nowrap' => true,
|
||||
]);
|
||||
$output .= $this->template->render('table/browse_foreigners/column_element', [
|
||||
'keyname' => $leftKeyname,
|
||||
'description' => $leftDescription,
|
||||
'title' => $leftDescriptionTitle,
|
||||
'is_selected' => $leftKeynameIsSelected,
|
||||
'nowrap' => false,
|
||||
]);
|
||||
|
||||
$output .= '<td width="20%">'
|
||||
. '<img src="' . $this->themeImage . 'spacer.png" alt=""'
|
||||
. ' width="1" height="1"></td>';
|
||||
|
||||
$output .= $this->template->render('table/browse_foreigners/column_element', [
|
||||
'keyname' => $rightKeyname,
|
||||
'description' => $rightDescription,
|
||||
'title' => $rightDescriptionTitle,
|
||||
'is_selected' => $rightKeynameIsSelected,
|
||||
'nowrap' => false,
|
||||
]);
|
||||
$output .= $this->template->render('table/browse_foreigners/column_element', [
|
||||
'keyname' => $rightKeyname,
|
||||
'description' => $rightDescription,
|
||||
'title' => $rightDescriptionTitle,
|
||||
'is_selected' => $rightKeynameIsSelected,
|
||||
'nowrap' => true,
|
||||
]);
|
||||
|
||||
$output .= '</tr>';
|
||||
|
||||
return [
|
||||
$output,
|
||||
$horizontal_count,
|
||||
$indexByDescription,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to get html for relational field selection
|
||||
*
|
||||
* @param string $db current database
|
||||
* @param string $table current table
|
||||
* @param string $field field
|
||||
* @param array $foreignData foreign column data
|
||||
* @param string|null $fieldkey field key
|
||||
* @param string $current_value current columns's value
|
||||
*/
|
||||
public function getHtmlForRelationalFieldSelection(
|
||||
string $db,
|
||||
string $table,
|
||||
string $field,
|
||||
array $foreignData,
|
||||
?string $fieldkey,
|
||||
string $current_value
|
||||
): string {
|
||||
$gotopage = $this->getHtmlForGotoPage($foreignData);
|
||||
$foreignShowAll = $this->template->render('table/browse_foreigners/show_all', [
|
||||
'foreign_data' => $foreignData,
|
||||
'show_all' => $this->showAll,
|
||||
'max_rows' => $this->maxRows,
|
||||
]);
|
||||
|
||||
$output = '<form class="ajax" '
|
||||
. 'id="browse_foreign_form" name="browse_foreign_from" action="'
|
||||
. Url::getFromRoute('/browse-foreigners')
|
||||
. '" method="post"><fieldset>'
|
||||
. Url::getHiddenInputs($db, $table)
|
||||
. '<input type="hidden" name="field" value="' . htmlspecialchars($field)
|
||||
. '">'
|
||||
. '<input type="hidden" name="fieldkey" value="'
|
||||
. (isset($fieldkey) ? htmlspecialchars($fieldkey) : '') . '">';
|
||||
|
||||
if (isset($_POST['rownumber'])) {
|
||||
$output .= '<input type="hidden" name="rownumber" value="'
|
||||
. htmlspecialchars((string) $_POST['rownumber']) . '">';
|
||||
}
|
||||
$filter_value = (isset($_POST['foreign_filter'])
|
||||
? htmlspecialchars($_POST['foreign_filter'])
|
||||
: '');
|
||||
$output .= '<span class="formelement">'
|
||||
. '<label for="input_foreign_filter">' . __('Search:') . '</label>'
|
||||
. '<input type="text" name="foreign_filter" '
|
||||
. 'id="input_foreign_filter" '
|
||||
. 'value="' . $filter_value . '" data-old="' . $filter_value . '" '
|
||||
. '>'
|
||||
. '<input class="btn btn-primary" type="submit" name="submit_foreign_filter" value="'
|
||||
. __('Go') . '">'
|
||||
. '</span>'
|
||||
. '<span class="formelement">' . $gotopage . '</span>'
|
||||
. '<span class="formelement">' . $foreignShowAll . '</span>'
|
||||
. '</fieldset>'
|
||||
. '</form>';
|
||||
|
||||
$output .= '<table class="pma-table" width="100%" id="browse_foreign_table">';
|
||||
|
||||
if (! is_array($foreignData['disp_row'])) {
|
||||
return $output . '</tbody>'
|
||||
. '</table>';
|
||||
}
|
||||
|
||||
$header = '<tr>
|
||||
<th>' . __('Keyname') . '</th>
|
||||
<th>' . __('Description') . '</th>
|
||||
<td width="20%"></td>
|
||||
<th>' . __('Description') . '</th>
|
||||
<th>' . __('Keyname') . '</th>
|
||||
</tr>';
|
||||
|
||||
$output .= '<thead>' . $header . '</thead>' . "\n"
|
||||
. '<tfoot>' . $header . '</tfoot>' . "\n"
|
||||
. '<tbody>' . "\n";
|
||||
|
||||
$descriptions = [];
|
||||
$keys = [];
|
||||
foreach ($foreignData['disp_row'] as $relrow) {
|
||||
if ($foreignData['foreign_display'] != false) {
|
||||
$descriptions[] = $relrow[$foreignData['foreign_display']] ?? '';
|
||||
} else {
|
||||
$descriptions[] = '';
|
||||
}
|
||||
|
||||
$keys[] = $relrow[$foreignData['foreign_field']];
|
||||
}
|
||||
|
||||
asort($keys);
|
||||
|
||||
$horizontal_count = 0;
|
||||
$indexByDescription = 0;
|
||||
|
||||
foreach ($keys as $indexByKeyname => $value) {
|
||||
[
|
||||
$html,
|
||||
$horizontal_count,
|
||||
$indexByDescription,
|
||||
] = $this->getHtmlForOneKey(
|
||||
$horizontal_count,
|
||||
$header,
|
||||
$keys,
|
||||
$indexByKeyname,
|
||||
$descriptions,
|
||||
$indexByDescription,
|
||||
$current_value
|
||||
);
|
||||
$output .= $html;
|
||||
}
|
||||
|
||||
$output .= '</tbody>'
|
||||
. '</table>';
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the description (possibly truncated) and the title
|
||||
*
|
||||
* @param string $description the key name's description
|
||||
*
|
||||
* @return array the new description and title
|
||||
*/
|
||||
private function getDescriptionAndTitle(string $description): array
|
||||
{
|
||||
if (mb_strlen($description) <= $this->limitChars) {
|
||||
$description = htmlspecialchars(
|
||||
$description
|
||||
);
|
||||
$descriptionTitle = '';
|
||||
} else {
|
||||
$descriptionTitle = htmlspecialchars(
|
||||
$description
|
||||
);
|
||||
$description = htmlspecialchars(
|
||||
mb_substr(
|
||||
$description,
|
||||
0,
|
||||
$this->limitChars
|
||||
)
|
||||
. '...'
|
||||
);
|
||||
}
|
||||
|
||||
return [
|
||||
$description,
|
||||
$descriptionTitle,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to get html for the goto page option
|
||||
*
|
||||
* @param array|null $foreignData foreign data
|
||||
*/
|
||||
private function getHtmlForGotoPage(?array $foreignData): string
|
||||
{
|
||||
$gotopage = '';
|
||||
isset($_POST['pos']) ? $pos = $_POST['pos'] : $pos = 0;
|
||||
if ($foreignData === null || ! is_array($foreignData['disp_row'])) {
|
||||
return $gotopage;
|
||||
}
|
||||
|
||||
$pageNow = (int) floor($pos / $this->maxRows) + 1;
|
||||
$nbTotalPage = (int) ceil($foreignData['the_total'] / $this->maxRows);
|
||||
|
||||
if ($foreignData['the_total'] > $this->maxRows) {
|
||||
$gotopage = Util::pageselector(
|
||||
'pos',
|
||||
$this->maxRows,
|
||||
$pageNow,
|
||||
$nbTotalPage,
|
||||
200,
|
||||
5,
|
||||
5,
|
||||
20,
|
||||
10,
|
||||
__('Page number:')
|
||||
);
|
||||
}
|
||||
|
||||
return $gotopage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to get foreign limit
|
||||
*
|
||||
* @param string|null $foreignShowAll foreign navigation
|
||||
*/
|
||||
public function getForeignLimit(?string $foreignShowAll): ?string
|
||||
{
|
||||
if (isset($foreignShowAll) && $foreignShowAll == __('Show all')) {
|
||||
return null;
|
||||
}
|
||||
isset($_POST['pos']) ? $pos = $_POST['pos'] : $pos = 0;
|
||||
|
||||
return 'LIMIT ' . $pos . ', ' . $this->maxRows . ' ';
|
||||
}
|
||||
}
|
227
admin/phpMyAdmin/libraries/classes/Charsets.php
Normal file
227
admin/phpMyAdmin/libraries/classes/Charsets.php
Normal file
|
@ -0,0 +1,227 @@
|
|||
<?php
|
||||
/**
|
||||
* MySQL charset metadata and manipulations
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin;
|
||||
|
||||
use PhpMyAdmin\Charsets\Charset;
|
||||
use PhpMyAdmin\Charsets\Collation;
|
||||
use const SORT_STRING;
|
||||
use function array_keys;
|
||||
use function count;
|
||||
use function explode;
|
||||
use function is_string;
|
||||
use function ksort;
|
||||
|
||||
/**
|
||||
* Class used to manage MySQL charsets
|
||||
*/
|
||||
class Charsets
|
||||
{
|
||||
/**
|
||||
* MySQL charsets map
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $mysqlCharsetMap = [
|
||||
'big5' => 'big5',
|
||||
'cp-866' => 'cp866',
|
||||
'euc-jp' => 'ujis',
|
||||
'euc-kr' => 'euckr',
|
||||
'gb2312' => 'gb2312',
|
||||
'gbk' => 'gbk',
|
||||
'iso-8859-1' => 'latin1',
|
||||
'iso-8859-2' => 'latin2',
|
||||
'iso-8859-7' => 'greek',
|
||||
'iso-8859-8' => 'hebrew',
|
||||
'iso-8859-8-i' => 'hebrew',
|
||||
'iso-8859-9' => 'latin5',
|
||||
'iso-8859-13' => 'latin7',
|
||||
'iso-8859-15' => 'latin1',
|
||||
'koi8-r' => 'koi8r',
|
||||
'shift_jis' => 'sjis',
|
||||
'tis-620' => 'tis620',
|
||||
'utf-8' => 'utf8',
|
||||
'windows-1250' => 'cp1250',
|
||||
'windows-1251' => 'cp1251',
|
||||
'windows-1252' => 'latin1',
|
||||
'windows-1256' => 'cp1256',
|
||||
'windows-1257' => 'cp1257',
|
||||
];
|
||||
|
||||
/**
|
||||
* The charset for the server
|
||||
*
|
||||
* @var Charset|null
|
||||
*/
|
||||
private static $serverCharset = null;
|
||||
|
||||
/** @var array<string, Charset> */
|
||||
private static $charsets = [];
|
||||
|
||||
/** @var array<string, array<string, Collation>> */
|
||||
private static $collations = [];
|
||||
|
||||
/**
|
||||
* Loads charset data from the server
|
||||
*
|
||||
* @param DatabaseInterface $dbi DatabaseInterface instance
|
||||
* @param bool $disableIs Disable use of INFORMATION_SCHEMA
|
||||
*/
|
||||
private static function loadCharsets(DatabaseInterface $dbi, bool $disableIs): void
|
||||
{
|
||||
/* Data already loaded */
|
||||
if (count(self::$charsets) > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($disableIs) {
|
||||
$sql = 'SHOW CHARACTER SET';
|
||||
} else {
|
||||
$sql = 'SELECT `CHARACTER_SET_NAME` AS `Charset`,'
|
||||
. ' `DEFAULT_COLLATE_NAME` AS `Default collation`,'
|
||||
. ' `DESCRIPTION` AS `Description`,'
|
||||
. ' `MAXLEN` AS `Maxlen`'
|
||||
. ' FROM `information_schema`.`CHARACTER_SETS`';
|
||||
}
|
||||
$res = $dbi->query($sql);
|
||||
|
||||
self::$charsets = [];
|
||||
while ($row = $dbi->fetchAssoc($res)) {
|
||||
self::$charsets[$row['Charset']] = Charset::fromServer($row);
|
||||
}
|
||||
$dbi->freeResult($res);
|
||||
|
||||
ksort(self::$charsets, SORT_STRING);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads collation data from the server
|
||||
*
|
||||
* @param DatabaseInterface $dbi DatabaseInterface instance
|
||||
* @param bool $disableIs Disable use of INFORMATION_SCHEMA
|
||||
*/
|
||||
private static function loadCollations(DatabaseInterface $dbi, bool $disableIs): void
|
||||
{
|
||||
/* Data already loaded */
|
||||
if (count(self::$collations) > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($disableIs) {
|
||||
$sql = 'SHOW COLLATION';
|
||||
} else {
|
||||
$sql = 'SELECT `COLLATION_NAME` AS `Collation`,'
|
||||
. ' `CHARACTER_SET_NAME` AS `Charset`,'
|
||||
. ' `ID` AS `Id`,'
|
||||
. ' `IS_DEFAULT` AS `Default`,'
|
||||
. ' `IS_COMPILED` AS `Compiled`,'
|
||||
. ' `SORTLEN` AS `Sortlen`'
|
||||
. ' FROM `information_schema`.`COLLATIONS`';
|
||||
}
|
||||
$res = $dbi->query($sql);
|
||||
|
||||
self::$collations = [];
|
||||
while ($row = $dbi->fetchAssoc($res)) {
|
||||
self::$collations[$row['Charset']][$row['Collation']] = Collation::fromServer($row);
|
||||
}
|
||||
$dbi->freeResult($res);
|
||||
|
||||
foreach (array_keys(self::$collations) as $charset) {
|
||||
ksort(self::$collations[$charset], SORT_STRING);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current server charset
|
||||
*
|
||||
* @param DatabaseInterface $dbi DatabaseInterface instance
|
||||
* @param bool $disableIs Disable use of INFORMATION_SCHEMA
|
||||
*/
|
||||
public static function getServerCharset(DatabaseInterface $dbi, bool $disableIs): Charset
|
||||
{
|
||||
if (self::$serverCharset !== null) {
|
||||
return self::$serverCharset;
|
||||
}
|
||||
self::loadCharsets($dbi, $disableIs);
|
||||
$serverCharset = $dbi->getVariable('character_set_server');
|
||||
if (! is_string($serverCharset)) {// MySQL 5.7.8 fallback, issue #15614
|
||||
$serverCharset = $dbi->fetchValue('SELECT @@character_set_server;');
|
||||
}
|
||||
|
||||
self::$serverCharset = self::$charsets[$serverCharset] ?? null;
|
||||
|
||||
// MySQL 8.0.11+ fallback, issue #16931
|
||||
if (self::$serverCharset === null && $serverCharset === 'utf8mb3') {
|
||||
// See: https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-11.html#mysqld-8-0-11-charset
|
||||
// The utf8mb3 character set will be replaced by utf8mb4 in a future MySQL version.
|
||||
// The utf8 character set is currently an alias for utf8mb3,
|
||||
// but will at that point become a reference to utf8mb4.
|
||||
// To avoid ambiguity about the meaning of utf8,
|
||||
// consider specifying utf8mb4 explicitly for character set references instead of utf8.
|
||||
// Warning: #3719 'utf8' is currently an alias for the character set UTF8MB3 [...]
|
||||
return self::$charsets['utf8'];
|
||||
}
|
||||
|
||||
if (self::$serverCharset === null) {// Fallback in case nothing is found
|
||||
return Charset::fromServer(
|
||||
[
|
||||
'Charset' => __('Unknown'),
|
||||
'Description' => __('Unknown'),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
return self::$serverCharset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all server charsets
|
||||
*
|
||||
* @param DatabaseInterface $dbi DatabaseInterface instance
|
||||
* @param bool $disableIs Disable use of INFORMATION_SCHEMA
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getCharsets(DatabaseInterface $dbi, bool $disableIs): array
|
||||
{
|
||||
self::loadCharsets($dbi, $disableIs);
|
||||
|
||||
return self::$charsets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all server collations
|
||||
*
|
||||
* @param DatabaseInterface $dbi DatabaseInterface instance
|
||||
* @param bool $disableIs Disable use of INFORMATION_SCHEMA
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getCollations(DatabaseInterface $dbi, bool $disableIs): array
|
||||
{
|
||||
self::loadCollations($dbi, $disableIs);
|
||||
|
||||
return self::$collations;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DatabaseInterface $dbi DatabaseInterface instance
|
||||
* @param bool $disableIs Disable use of INFORMATION_SCHEMA
|
||||
* @param string|null $name Collation name
|
||||
*/
|
||||
public static function findCollationByName(DatabaseInterface $dbi, bool $disableIs, ?string $name): ?Collation
|
||||
{
|
||||
$pieces = explode('_', (string) $name);
|
||||
if ($pieces === false || ! isset($pieces[0])) {
|
||||
return null;
|
||||
}
|
||||
$charset = $pieces[0];
|
||||
$collations = self::getCollations($dbi, $disableIs);
|
||||
|
||||
return $collations[$charset][$name] ?? null;
|
||||
}
|
||||
}
|
95
admin/phpMyAdmin/libraries/classes/Charsets/Charset.php
Normal file
95
admin/phpMyAdmin/libraries/classes/Charsets/Charset.php
Normal file
|
@ -0,0 +1,95 @@
|
|||
<?php
|
||||
/**
|
||||
* Value object class for a character set
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Charsets;
|
||||
|
||||
/**
|
||||
* Value object class for a character set
|
||||
*/
|
||||
final class Charset
|
||||
{
|
||||
/**
|
||||
* The character set name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $name;
|
||||
|
||||
/**
|
||||
* A description of the character set
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $description;
|
||||
|
||||
/**
|
||||
* The default collation for the character set
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $defaultCollation;
|
||||
|
||||
/**
|
||||
* The maximum number of bytes required to store one character
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $maxLength;
|
||||
|
||||
/**
|
||||
* @param string $name Charset name
|
||||
* @param string $description Description
|
||||
* @param string $defaultCollation Default collation
|
||||
* @param int $maxLength Maximum length
|
||||
*/
|
||||
private function __construct(
|
||||
string $name,
|
||||
string $description,
|
||||
string $defaultCollation,
|
||||
int $maxLength
|
||||
) {
|
||||
$this->name = $name;
|
||||
$this->description = $description;
|
||||
$this->defaultCollation = $defaultCollation;
|
||||
$this->maxLength = $maxLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $state State obtained from the database server
|
||||
*
|
||||
* @return Charset
|
||||
*/
|
||||
public static function fromServer(array $state): self
|
||||
{
|
||||
return new self(
|
||||
$state['Charset'] ?? '',
|
||||
$state['Description'] ?? '',
|
||||
$state['Default collation'] ?? '',
|
||||
(int) ($state['Maxlen'] ?? 0)
|
||||
);
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getDescription(): string
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
public function getDefaultCollation(): string
|
||||
{
|
||||
return $this->defaultCollation;
|
||||
}
|
||||
|
||||
public function getMaxLength(): int
|
||||
{
|
||||
return $this->maxLength;
|
||||
}
|
||||
}
|
587
admin/phpMyAdmin/libraries/classes/Charsets/Collation.php
Normal file
587
admin/phpMyAdmin/libraries/classes/Charsets/Collation.php
Normal file
|
@ -0,0 +1,587 @@
|
|||
<?php
|
||||
/**
|
||||
* Value object class for a collation
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Charsets;
|
||||
|
||||
use function count;
|
||||
use function explode;
|
||||
use function implode;
|
||||
|
||||
/**
|
||||
* Value object class for a collation
|
||||
*/
|
||||
final class Collation
|
||||
{
|
||||
/**
|
||||
* The collation name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $name;
|
||||
|
||||
/**
|
||||
* A description of the collation
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $description;
|
||||
|
||||
/**
|
||||
* The name of the character set with which the collation is associated
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $charset;
|
||||
|
||||
/**
|
||||
* The collation ID
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* Whether the collation is the default for its character set
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $isDefault;
|
||||
|
||||
/**
|
||||
* Whether the character set is compiled into the server
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $isCompiled;
|
||||
|
||||
/**
|
||||
* Used for determining the memory used to sort strings in this collation
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $sortLength;
|
||||
|
||||
/**
|
||||
* The collation pad attribute
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $padAttribute;
|
||||
|
||||
/**
|
||||
* @param string $name Collation name
|
||||
* @param string $charset Related charset
|
||||
* @param int $id Collation ID
|
||||
* @param bool $isDefault Whether is the default
|
||||
* @param bool $isCompiled Whether the charset is compiled
|
||||
* @param int $sortLength Sort length
|
||||
* @param string $padAttribute Pad attribute
|
||||
*/
|
||||
private function __construct(
|
||||
string $name,
|
||||
string $charset,
|
||||
int $id,
|
||||
bool $isDefault,
|
||||
bool $isCompiled,
|
||||
int $sortLength,
|
||||
string $padAttribute
|
||||
) {
|
||||
$this->name = $name;
|
||||
$this->charset = $charset;
|
||||
$this->id = $id;
|
||||
$this->isDefault = $isDefault;
|
||||
$this->isCompiled = $isCompiled;
|
||||
$this->sortLength = $sortLength;
|
||||
$this->padAttribute = $padAttribute;
|
||||
$this->description = $this->buildDescription();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $state State obtained from the database server
|
||||
*/
|
||||
public static function fromServer(array $state): self
|
||||
{
|
||||
return new self(
|
||||
$state['Collation'] ?? '',
|
||||
$state['Charset'] ?? '',
|
||||
(int) ($state['Id'] ?? 0),
|
||||
isset($state['Default']) && ($state['Default'] === 'Yes' || $state['Default'] === '1'),
|
||||
isset($state['Compiled']) && ($state['Compiled'] === 'Yes' || $state['Compiled'] === '1'),
|
||||
(int) ($state['Sortlen'] ?? 0),
|
||||
$state['Pad_attribute'] ?? ''
|
||||
);
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getDescription(): string
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
public function getCharset(): string
|
||||
{
|
||||
return $this->charset;
|
||||
}
|
||||
|
||||
public function getId(): int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function isDefault(): bool
|
||||
{
|
||||
return $this->isDefault;
|
||||
}
|
||||
|
||||
public function isCompiled(): bool
|
||||
{
|
||||
return $this->isCompiled;
|
||||
}
|
||||
|
||||
public function getSortLength(): int
|
||||
{
|
||||
return $this->sortLength;
|
||||
}
|
||||
|
||||
public function getPadAttribute(): string
|
||||
{
|
||||
return $this->padAttribute;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns description for given collation
|
||||
*
|
||||
* @return string collation description
|
||||
*/
|
||||
private function buildDescription(): string
|
||||
{
|
||||
$parts = explode('_', $this->getName());
|
||||
|
||||
$name = __('Unknown');
|
||||
$variant = null;
|
||||
$suffixes = [];
|
||||
$unicode = false;
|
||||
$unknown = false;
|
||||
|
||||
$level = 0;
|
||||
foreach ($parts as $part) {
|
||||
if ($level === 0) {
|
||||
/* Next will be language */
|
||||
$level = 1;
|
||||
/* First should be charset */
|
||||
$this->getNameForLevel0(
|
||||
$unicode, // By reference
|
||||
$unknown, // Same
|
||||
$part,
|
||||
$name, // By reference
|
||||
$variant// Same
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if ($level === 1) {
|
||||
/* Next will be variant unless changed later */
|
||||
$level = 4;
|
||||
/* Locale name or code */
|
||||
$found = true;
|
||||
$this->getNameForLevel1(
|
||||
$unicode,
|
||||
$unknown,
|
||||
$part,
|
||||
$name, // By reference, will be changed
|
||||
$level, // Also
|
||||
$found// Same
|
||||
);
|
||||
if ($found) {
|
||||
continue;
|
||||
}
|
||||
// Not parsed token, fall to next level
|
||||
}
|
||||
if ($level === 2) {
|
||||
/* Next will be variant */
|
||||
$level = 4;
|
||||
/* Germal variant */
|
||||
if ($part === 'pb') {
|
||||
$name = _pgettext('Collation', 'German (phone book order)');
|
||||
continue;
|
||||
}
|
||||
$name = _pgettext('Collation', 'German (dictionary order)');
|
||||
// Not parsed token, fall to next level
|
||||
}
|
||||
if ($level === 3) {
|
||||
/* Next will be variant */
|
||||
$level = 4;
|
||||
/* Spanish variant */
|
||||
if ($part === 'trad') {
|
||||
$name = _pgettext('Collation', 'Spanish (traditional)');
|
||||
continue;
|
||||
}
|
||||
$name = _pgettext('Collation', 'Spanish (modern)');
|
||||
// Not parsed token, fall to next level
|
||||
}
|
||||
if ($level === 4) {
|
||||
/* Next will be suffix */
|
||||
$level = 5;
|
||||
/* Variant */
|
||||
$found = true;
|
||||
$variantFound = $this->getVariant($part);
|
||||
if ($variantFound === null) {
|
||||
$found = false;
|
||||
} else {
|
||||
$variant = $variantFound;
|
||||
}
|
||||
if ($found) {
|
||||
continue;
|
||||
}
|
||||
// Not parsed token, fall to next level
|
||||
}
|
||||
if ($level < 5) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Suffixes */
|
||||
$suffixes = $this->addSuffixes($suffixes, $part);
|
||||
}
|
||||
|
||||
return $this->buildName($name, $variant, $suffixes);
|
||||
}
|
||||
|
||||
private function buildName(string $result, ?string $variant, array $suffixes): string
|
||||
{
|
||||
if ($variant !== null) {
|
||||
$result .= ' (' . $variant . ')';
|
||||
}
|
||||
if (count($suffixes) > 0) {
|
||||
$result .= ', ' . implode(', ', $suffixes);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function getVariant(string $part): ?string
|
||||
{
|
||||
switch ($part) {
|
||||
case '0900':
|
||||
return 'UCA 9.0.0';
|
||||
case '520':
|
||||
return 'UCA 5.2.0';
|
||||
case 'mysql561':
|
||||
return 'MySQL 5.6.1';
|
||||
case 'mysql500':
|
||||
return 'MySQL 5.0.0';
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private function addSuffixes(array $suffixes, string $part): array
|
||||
{
|
||||
switch ($part) {
|
||||
case 'ci':
|
||||
$suffixes[] = _pgettext('Collation variant', 'case-insensitive');
|
||||
break;
|
||||
case 'cs':
|
||||
$suffixes[] = _pgettext('Collation variant', 'case-sensitive');
|
||||
break;
|
||||
case 'ai':
|
||||
$suffixes[] = _pgettext('Collation variant', 'accent-insensitive');
|
||||
break;
|
||||
case 'as':
|
||||
$suffixes[] = _pgettext('Collation variant', 'accent-sensitive');
|
||||
break;
|
||||
case 'ks':
|
||||
$suffixes[] = _pgettext('Collation variant', 'kana-sensitive');
|
||||
break;
|
||||
case 'w2':
|
||||
case 'l2':
|
||||
$suffixes[] = _pgettext('Collation variant', 'multi-level');
|
||||
break;
|
||||
case 'bin':
|
||||
$suffixes[] = _pgettext('Collation variant', 'binary');
|
||||
break;
|
||||
case 'nopad':
|
||||
$suffixes[] = _pgettext('Collation variant', 'no-pad');
|
||||
break;
|
||||
}
|
||||
|
||||
return $suffixes;
|
||||
}
|
||||
|
||||
private function getNameForLevel0(
|
||||
bool &$unicode,
|
||||
bool &$unknown,
|
||||
string $part,
|
||||
?string &$name,
|
||||
?string &$variant
|
||||
): void {
|
||||
switch ($part) {
|
||||
case 'binary':
|
||||
$name = _pgettext('Collation', 'Binary');
|
||||
break;
|
||||
// Unicode charsets
|
||||
case 'utf8mb4':
|
||||
$variant = 'UCA 4.0.0';
|
||||
// Fall through to other unicode
|
||||
case 'ucs2':
|
||||
case 'utf8':
|
||||
case 'utf16':
|
||||
case 'utf16le':
|
||||
case 'utf16be':
|
||||
case 'utf32':
|
||||
$name = _pgettext('Collation', 'Unicode');
|
||||
$unicode = true;
|
||||
break;
|
||||
// West European charsets
|
||||
case 'ascii':
|
||||
case 'cp850':
|
||||
case 'dec8':
|
||||
case 'hp8':
|
||||
case 'latin1':
|
||||
case 'macroman':
|
||||
$name = _pgettext('Collation', 'West European');
|
||||
break;
|
||||
// Central European charsets
|
||||
case 'cp1250':
|
||||
case 'cp852':
|
||||
case 'latin2':
|
||||
case 'macce':
|
||||
$name = _pgettext('Collation', 'Central European');
|
||||
break;
|
||||
// Russian charsets
|
||||
case 'cp866':
|
||||
case 'koi8r':
|
||||
$name = _pgettext('Collation', 'Russian');
|
||||
break;
|
||||
// Chinese charsets
|
||||
case 'gb2312':
|
||||
case 'gbk':
|
||||
$name = _pgettext('Collation', 'Simplified Chinese');
|
||||
break;
|
||||
case 'big5':
|
||||
$name = _pgettext('Collation', 'Traditional Chinese');
|
||||
break;
|
||||
case 'gb18030':
|
||||
$name = _pgettext('Collation', 'Chinese');
|
||||
$unicode = true;
|
||||
break;
|
||||
// Japanese charsets
|
||||
case 'sjis':
|
||||
case 'ujis':
|
||||
case 'cp932':
|
||||
case 'eucjpms':
|
||||
$name = _pgettext('Collation', 'Japanese');
|
||||
break;
|
||||
// Baltic charsets
|
||||
case 'cp1257':
|
||||
case 'latin7':
|
||||
$name = _pgettext('Collation', 'Baltic');
|
||||
break;
|
||||
// Other
|
||||
case 'armscii8':
|
||||
case 'armscii':
|
||||
$name = _pgettext('Collation', 'Armenian');
|
||||
break;
|
||||
case 'cp1251':
|
||||
$name = _pgettext('Collation', 'Cyrillic');
|
||||
break;
|
||||
case 'cp1256':
|
||||
$name = _pgettext('Collation', 'Arabic');
|
||||
break;
|
||||
case 'euckr':
|
||||
$name = _pgettext('Collation', 'Korean');
|
||||
break;
|
||||
case 'hebrew':
|
||||
$name = _pgettext('Collation', 'Hebrew');
|
||||
break;
|
||||
case 'geostd8':
|
||||
$name = _pgettext('Collation', 'Georgian');
|
||||
break;
|
||||
case 'greek':
|
||||
$name = _pgettext('Collation', 'Greek');
|
||||
break;
|
||||
case 'keybcs2':
|
||||
$name = _pgettext('Collation', 'Czech-Slovak');
|
||||
break;
|
||||
case 'koi8u':
|
||||
$name = _pgettext('Collation', 'Ukrainian');
|
||||
break;
|
||||
case 'latin5':
|
||||
$name = _pgettext('Collation', 'Turkish');
|
||||
break;
|
||||
case 'swe7':
|
||||
$name = _pgettext('Collation', 'Swedish');
|
||||
break;
|
||||
case 'tis620':
|
||||
$name = _pgettext('Collation', 'Thai');
|
||||
break;
|
||||
default:
|
||||
$name = _pgettext('Collation', 'Unknown');
|
||||
$unknown = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private function getNameForLevel1(
|
||||
bool $unicode,
|
||||
bool $unknown,
|
||||
string $part,
|
||||
?string &$name,
|
||||
int &$level,
|
||||
bool &$found
|
||||
): void {
|
||||
switch ($part) {
|
||||
case 'general':
|
||||
break;
|
||||
case 'bulgarian':
|
||||
case 'bg':
|
||||
$name = _pgettext('Collation', 'Bulgarian');
|
||||
break;
|
||||
case 'chinese':
|
||||
case 'cn':
|
||||
case 'zh':
|
||||
if ($unicode) {
|
||||
$name = _pgettext('Collation', 'Chinese');
|
||||
}
|
||||
break;
|
||||
case 'croatian':
|
||||
case 'hr':
|
||||
$name = _pgettext('Collation', 'Croatian');
|
||||
break;
|
||||
case 'czech':
|
||||
case 'cs':
|
||||
$name = _pgettext('Collation', 'Czech');
|
||||
break;
|
||||
case 'danish':
|
||||
case 'da':
|
||||
$name = _pgettext('Collation', 'Danish');
|
||||
break;
|
||||
case 'english':
|
||||
case 'en':
|
||||
$name = _pgettext('Collation', 'English');
|
||||
break;
|
||||
case 'esperanto':
|
||||
case 'eo':
|
||||
$name = _pgettext('Collation', 'Esperanto');
|
||||
break;
|
||||
case 'estonian':
|
||||
case 'et':
|
||||
$name = _pgettext('Collation', 'Estonian');
|
||||
break;
|
||||
case 'german1':
|
||||
$name = _pgettext('Collation', 'German (dictionary order)');
|
||||
break;
|
||||
case 'german2':
|
||||
$name = _pgettext('Collation', 'German (phone book order)');
|
||||
break;
|
||||
case 'german':
|
||||
case 'de':
|
||||
/* Name is set later */
|
||||
$level = 2;
|
||||
break;
|
||||
case 'hungarian':
|
||||
case 'hu':
|
||||
$name = _pgettext('Collation', 'Hungarian');
|
||||
break;
|
||||
case 'icelandic':
|
||||
case 'is':
|
||||
$name = _pgettext('Collation', 'Icelandic');
|
||||
break;
|
||||
case 'japanese':
|
||||
case 'ja':
|
||||
$name = _pgettext('Collation', 'Japanese');
|
||||
break;
|
||||
case 'la':
|
||||
$name = _pgettext('Collation', 'Classical Latin');
|
||||
break;
|
||||
case 'latvian':
|
||||
case 'lv':
|
||||
$name = _pgettext('Collation', 'Latvian');
|
||||
break;
|
||||
case 'lithuanian':
|
||||
case 'lt':
|
||||
$name = _pgettext('Collation', 'Lithuanian');
|
||||
break;
|
||||
case 'korean':
|
||||
case 'ko':
|
||||
$name = _pgettext('Collation', 'Korean');
|
||||
break;
|
||||
case 'myanmar':
|
||||
case 'my':
|
||||
$name = _pgettext('Collation', 'Burmese');
|
||||
break;
|
||||
case 'persian':
|
||||
$name = _pgettext('Collation', 'Persian');
|
||||
break;
|
||||
case 'polish':
|
||||
case 'pl':
|
||||
$name = _pgettext('Collation', 'Polish');
|
||||
break;
|
||||
case 'roman':
|
||||
$name = _pgettext('Collation', 'West European');
|
||||
break;
|
||||
case 'romanian':
|
||||
case 'ro':
|
||||
$name = _pgettext('Collation', 'Romanian');
|
||||
break;
|
||||
case 'ru':
|
||||
$name = _pgettext('Collation', 'Russian');
|
||||
break;
|
||||
case 'si':
|
||||
case 'sinhala':
|
||||
$name = _pgettext('Collation', 'Sinhalese');
|
||||
break;
|
||||
case 'slovak':
|
||||
case 'sk':
|
||||
$name = _pgettext('Collation', 'Slovak');
|
||||
break;
|
||||
case 'slovenian':
|
||||
case 'sl':
|
||||
$name = _pgettext('Collation', 'Slovenian');
|
||||
break;
|
||||
case 'spanish':
|
||||
$name = _pgettext('Collation', 'Spanish (modern)');
|
||||
break;
|
||||
case 'es':
|
||||
/* Name is set later */
|
||||
$level = 3;
|
||||
break;
|
||||
case 'spanish2':
|
||||
$name = _pgettext('Collation', 'Spanish (traditional)');
|
||||
break;
|
||||
case 'swedish':
|
||||
case 'sv':
|
||||
$name = _pgettext('Collation', 'Swedish');
|
||||
break;
|
||||
case 'thai':
|
||||
case 'th':
|
||||
$name = _pgettext('Collation', 'Thai');
|
||||
break;
|
||||
case 'turkish':
|
||||
case 'tr':
|
||||
$name = _pgettext('Collation', 'Turkish');
|
||||
break;
|
||||
case 'ukrainian':
|
||||
case 'uk':
|
||||
$name = _pgettext('Collation', 'Ukrainian');
|
||||
break;
|
||||
case 'vietnamese':
|
||||
case 'vi':
|
||||
$name = _pgettext('Collation', 'Vietnamese');
|
||||
break;
|
||||
case 'unicode':
|
||||
if ($unknown) {
|
||||
$name = _pgettext('Collation', 'Unicode');
|
||||
}
|
||||
break;
|
||||
default:
|
||||
$found = false;
|
||||
}
|
||||
}
|
||||
}
|
370
admin/phpMyAdmin/libraries/classes/CheckUserPrivileges.php
Normal file
370
admin/phpMyAdmin/libraries/classes/CheckUserPrivileges.php
Normal file
|
@ -0,0 +1,370 @@
|
|||
<?php
|
||||
/**
|
||||
* Get user's global privileges and some db-specific privileges
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin;
|
||||
|
||||
use PhpMyAdmin\Query\Utilities;
|
||||
use PhpMyAdmin\Utils\SessionCache;
|
||||
use function mb_strpos;
|
||||
use function mb_substr;
|
||||
use function preg_match;
|
||||
use function preg_replace;
|
||||
use function strpos;
|
||||
|
||||
/**
|
||||
* PhpMyAdmin\CheckUserPrivileges class
|
||||
*/
|
||||
class CheckUserPrivileges
|
||||
{
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param DatabaseInterface $dbi DatabaseInterface object
|
||||
*/
|
||||
public function __construct(DatabaseInterface $dbi)
|
||||
{
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts details from a result row of a SHOW GRANT query
|
||||
*
|
||||
* @param string $row grant row
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getItemsFromShowGrantsRow(string $row): array
|
||||
{
|
||||
$db_name_offset = mb_strpos($row, ' ON ') + 4;
|
||||
|
||||
$tblname_end_offset = mb_strpos($row, ' TO ');
|
||||
$tblname_start_offset = false;
|
||||
$__tblname_start_offset = mb_strpos($row, '`.', $db_name_offset);
|
||||
|
||||
if ($__tblname_start_offset && $__tblname_start_offset < $tblname_end_offset) {
|
||||
$tblname_start_offset = $__tblname_start_offset + 1;
|
||||
}
|
||||
|
||||
if ($tblname_start_offset === false) {
|
||||
$tblname_start_offset = mb_strpos($row, '.', $db_name_offset);
|
||||
}
|
||||
|
||||
$show_grants_dbname = mb_substr(
|
||||
$row,
|
||||
$db_name_offset,
|
||||
$tblname_start_offset - $db_name_offset
|
||||
);
|
||||
|
||||
$show_grants_dbname = Util::unQuote($show_grants_dbname, '`');
|
||||
|
||||
$show_grants_str = mb_substr(
|
||||
$row,
|
||||
6,
|
||||
mb_strpos($row, ' ON ') - 6
|
||||
);
|
||||
|
||||
$show_grants_tblname = mb_substr(
|
||||
$row,
|
||||
$tblname_start_offset + 1,
|
||||
$tblname_end_offset - $tblname_start_offset - 1
|
||||
);
|
||||
$show_grants_tblname = Util::unQuote($show_grants_tblname, '`');
|
||||
|
||||
return [
|
||||
$show_grants_str,
|
||||
$show_grants_dbname,
|
||||
$show_grants_tblname,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user has required privileges for
|
||||
* performing 'Adjust privileges' operations
|
||||
*
|
||||
* @param string $show_grants_str string containing grants for user
|
||||
* @param string $show_grants_dbname name of db extracted from grant string
|
||||
* @param string $show_grants_tblname name of table extracted from grant string
|
||||
*/
|
||||
public function checkRequiredPrivilegesForAdjust(
|
||||
string $show_grants_str,
|
||||
string $show_grants_dbname,
|
||||
string $show_grants_tblname
|
||||
): void {
|
||||
// '... ALL PRIVILEGES ON *.* ...' OR '... ALL PRIVILEGES ON `mysql`.* ..'
|
||||
// OR
|
||||
// SELECT, INSERT, UPDATE, DELETE .... ON *.* OR `mysql`.*
|
||||
if ($show_grants_str !== 'ALL'
|
||||
&& $show_grants_str !== 'ALL PRIVILEGES'
|
||||
&& (mb_strpos(
|
||||
$show_grants_str,
|
||||
'SELECT, INSERT, UPDATE, DELETE'
|
||||
) === false)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($show_grants_dbname === '*'
|
||||
&& $show_grants_tblname === '*'
|
||||
) {
|
||||
$GLOBALS['col_priv'] = true;
|
||||
$GLOBALS['db_priv'] = true;
|
||||
$GLOBALS['proc_priv'] = true;
|
||||
$GLOBALS['table_priv'] = true;
|
||||
|
||||
if ($show_grants_str === 'ALL PRIVILEGES'
|
||||
|| $show_grants_str === 'ALL'
|
||||
) {
|
||||
$GLOBALS['is_reload_priv'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// check for specific tables in `mysql` db
|
||||
// Ex. '... ALL PRIVILEGES on `mysql`.`columns_priv` .. '
|
||||
if ($show_grants_dbname !== 'mysql') {
|
||||
return;
|
||||
}
|
||||
|
||||
switch ($show_grants_tblname) {
|
||||
case 'columns_priv':
|
||||
$GLOBALS['col_priv'] = true;
|
||||
break;
|
||||
case 'db':
|
||||
$GLOBALS['db_priv'] = true;
|
||||
break;
|
||||
case 'procs_priv':
|
||||
$GLOBALS['proc_priv'] = true;
|
||||
break;
|
||||
case 'tables_priv':
|
||||
$GLOBALS['table_priv'] = true;
|
||||
break;
|
||||
case '*':
|
||||
$GLOBALS['col_priv'] = true;
|
||||
$GLOBALS['db_priv'] = true;
|
||||
$GLOBALS['proc_priv'] = true;
|
||||
$GLOBALS['table_priv'] = true;
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* sets privilege information extracted from SHOW GRANTS result
|
||||
*
|
||||
* Detection for some CREATE privilege.
|
||||
*
|
||||
* Since MySQL 4.1.2, we can easily detect current user's grants using $userlink
|
||||
* (no control user needed) and we don't have to try any other method for
|
||||
* detection
|
||||
*
|
||||
* @todo fix to get really all privileges, not only explicitly defined for this user
|
||||
* from MySQL manual: (https://dev.mysql.com/doc/refman/5.0/en/show-grants.html)
|
||||
* SHOW GRANTS displays only the privileges granted explicitly to the named
|
||||
* account. Other privileges might be available to the account, but they are not
|
||||
* displayed. For example, if an anonymous account exists, the named account
|
||||
* might be able to use its privileges, but SHOW GRANTS will not display them.
|
||||
*/
|
||||
private function analyseShowGrant(): void
|
||||
{
|
||||
if (SessionCache::has('is_create_db_priv')) {
|
||||
$GLOBALS['is_create_db_priv'] = SessionCache::get(
|
||||
'is_create_db_priv'
|
||||
);
|
||||
$GLOBALS['is_reload_priv'] = SessionCache::get(
|
||||
'is_reload_priv'
|
||||
);
|
||||
$GLOBALS['db_to_create'] = SessionCache::get(
|
||||
'db_to_create'
|
||||
);
|
||||
$GLOBALS['dbs_where_create_table_allowed'] = SessionCache::get(
|
||||
'dbs_where_create_table_allowed'
|
||||
);
|
||||
$GLOBALS['dbs_to_test'] = SessionCache::get(
|
||||
'dbs_to_test'
|
||||
);
|
||||
|
||||
$GLOBALS['db_priv'] = SessionCache::get(
|
||||
'db_priv'
|
||||
);
|
||||
$GLOBALS['col_priv'] = SessionCache::get(
|
||||
'col_priv'
|
||||
);
|
||||
$GLOBALS['table_priv'] = SessionCache::get(
|
||||
'table_priv'
|
||||
);
|
||||
$GLOBALS['proc_priv'] = SessionCache::get(
|
||||
'proc_priv'
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// defaults
|
||||
$GLOBALS['is_create_db_priv'] = false;
|
||||
$GLOBALS['is_reload_priv'] = false;
|
||||
$GLOBALS['db_to_create'] = '';
|
||||
$GLOBALS['dbs_where_create_table_allowed'] = [];
|
||||
$GLOBALS['dbs_to_test'] = Utilities::getSystemSchemas();
|
||||
$GLOBALS['proc_priv'] = false;
|
||||
$GLOBALS['db_priv'] = false;
|
||||
$GLOBALS['col_priv'] = false;
|
||||
$GLOBALS['table_priv'] = false;
|
||||
|
||||
$rs_usr = $this->dbi->tryQuery('SHOW GRANTS');
|
||||
|
||||
if (! $rs_usr) {
|
||||
return;
|
||||
}
|
||||
|
||||
$re0 = '(^|(\\\\\\\\)+|[^\\\\])'; // non-escaped wildcards
|
||||
$re1 = '(^|[^\\\\])(\\\)+'; // escaped wildcards
|
||||
|
||||
while ($row = $this->dbi->fetchRow($rs_usr)) {
|
||||
[
|
||||
$show_grants_str,
|
||||
$show_grants_dbname,
|
||||
$show_grants_tblname,
|
||||
] = $this->getItemsFromShowGrantsRow($row[0]);
|
||||
|
||||
if ($show_grants_dbname === '*') {
|
||||
if ($show_grants_str !== 'USAGE') {
|
||||
$GLOBALS['dbs_to_test'] = false;
|
||||
}
|
||||
} elseif ($GLOBALS['dbs_to_test'] !== false) {
|
||||
$GLOBALS['dbs_to_test'][] = $show_grants_dbname;
|
||||
}
|
||||
|
||||
if (mb_strpos($show_grants_str, 'RELOAD') !== false) {
|
||||
$GLOBALS['is_reload_priv'] = true;
|
||||
}
|
||||
|
||||
// check for the required privileges for adjust
|
||||
$this->checkRequiredPrivilegesForAdjust(
|
||||
$show_grants_str,
|
||||
$show_grants_dbname,
|
||||
$show_grants_tblname
|
||||
);
|
||||
|
||||
/**
|
||||
* @todo if we find CREATE VIEW but not CREATE, do not offer
|
||||
* the create database dialog box
|
||||
*/
|
||||
if ($show_grants_str !== 'ALL'
|
||||
&& $show_grants_str !== 'ALL PRIVILEGES'
|
||||
&& $show_grants_str !== 'CREATE'
|
||||
&& strpos($show_grants_str, 'CREATE,') === false
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($show_grants_dbname === '*') {
|
||||
// a global CREATE privilege
|
||||
$GLOBALS['is_create_db_priv'] = true;
|
||||
$GLOBALS['is_reload_priv'] = true;
|
||||
$GLOBALS['db_to_create'] = '';
|
||||
$GLOBALS['dbs_where_create_table_allowed'][] = '*';
|
||||
// @todo we should not break here, cause GRANT ALL *.*
|
||||
// could be revoked by a later rule like GRANT SELECT ON db.*
|
||||
break;
|
||||
}
|
||||
|
||||
// this array may contain wildcards
|
||||
$GLOBALS['dbs_where_create_table_allowed'][] = $show_grants_dbname;
|
||||
|
||||
$dbname_to_test = Util::backquote($show_grants_dbname);
|
||||
|
||||
if ($GLOBALS['is_create_db_priv']) {
|
||||
// no need for any more tests if we already know this
|
||||
continue;
|
||||
}
|
||||
|
||||
// does this db exist?
|
||||
if ((! preg_match('/' . $re0 . '%|_/', $show_grants_dbname)
|
||||
|| preg_match('/\\\\%|\\\\_/', $show_grants_dbname))
|
||||
&& ($this->dbi->tryQuery(
|
||||
'USE ' . preg_replace(
|
||||
'/' . $re1 . '(%|_)/',
|
||||
'\\1\\3',
|
||||
$dbname_to_test
|
||||
)
|
||||
)
|
||||
|| mb_substr((string) $this->dbi->getError(), 1, 4) == 1044)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do not handle the underscore wildcard
|
||||
* (this case must be rare anyway)
|
||||
*/
|
||||
$GLOBALS['db_to_create'] = preg_replace(
|
||||
'/' . $re0 . '%/',
|
||||
'\\1',
|
||||
$show_grants_dbname
|
||||
);
|
||||
$GLOBALS['db_to_create'] = preg_replace(
|
||||
'/' . $re1 . '(%|_)/',
|
||||
'\\1\\3',
|
||||
$GLOBALS['db_to_create']
|
||||
);
|
||||
$GLOBALS['is_create_db_priv'] = true;
|
||||
|
||||
/**
|
||||
* @todo collect $GLOBALS['db_to_create'] into an array,
|
||||
* to display a drop-down in the "Create database" dialog
|
||||
*/
|
||||
// we don't break, we want all possible databases
|
||||
//break;
|
||||
}
|
||||
|
||||
$this->dbi->freeResult($rs_usr);
|
||||
|
||||
// must also cacheUnset() them in
|
||||
// PhpMyAdmin\Plugins\Auth\AuthenticationCookie
|
||||
SessionCache::set('is_create_db_priv', $GLOBALS['is_create_db_priv']);
|
||||
SessionCache::set('is_reload_priv', $GLOBALS['is_reload_priv']);
|
||||
SessionCache::set('db_to_create', $GLOBALS['db_to_create']);
|
||||
SessionCache::set(
|
||||
'dbs_where_create_table_allowed',
|
||||
$GLOBALS['dbs_where_create_table_allowed']
|
||||
);
|
||||
SessionCache::set('dbs_to_test', $GLOBALS['dbs_to_test']);
|
||||
|
||||
SessionCache::set('proc_priv', $GLOBALS['proc_priv']);
|
||||
SessionCache::set('table_priv', $GLOBALS['table_priv']);
|
||||
SessionCache::set('col_priv', $GLOBALS['col_priv']);
|
||||
SessionCache::set('db_priv', $GLOBALS['db_priv']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user's global privileges and some db-specific privileges
|
||||
*/
|
||||
public function getPrivileges(): void
|
||||
{
|
||||
$username = '';
|
||||
|
||||
$current = $this->dbi->getCurrentUserAndHost();
|
||||
if (! empty($current)) {
|
||||
[$username] = $current;
|
||||
}
|
||||
|
||||
// If MySQL is started with --skip-grant-tables
|
||||
if ($username === '') {
|
||||
$GLOBALS['is_create_db_priv'] = true;
|
||||
$GLOBALS['is_reload_priv'] = true;
|
||||
$GLOBALS['db_to_create'] = '';
|
||||
$GLOBALS['dbs_where_create_table_allowed'] = ['*'];
|
||||
$GLOBALS['dbs_to_test'] = false;
|
||||
$GLOBALS['db_priv'] = true;
|
||||
$GLOBALS['col_priv'] = true;
|
||||
$GLOBALS['table_priv'] = true;
|
||||
$GLOBALS['proc_priv'] = true;
|
||||
} else {
|
||||
$this->analyseShowGrant();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,188 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Command;
|
||||
|
||||
use PhpMyAdmin\Config;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Routing;
|
||||
use PhpMyAdmin\Tests\Stubs\DbiDummy;
|
||||
use PhpMyAdmin\Twig\CoreExtension;
|
||||
use PhpMyAdmin\Twig\I18nExtension;
|
||||
use PhpMyAdmin\Twig\MessageExtension;
|
||||
use PhpMyAdmin\Twig\PluginsExtension;
|
||||
use PhpMyAdmin\Twig\RelationExtension;
|
||||
use PhpMyAdmin\Twig\SanitizeExtension;
|
||||
use PhpMyAdmin\Twig\TableExtension;
|
||||
use PhpMyAdmin\Twig\TrackerExtension;
|
||||
use PhpMyAdmin\Twig\TransformationsExtension;
|
||||
use PhpMyAdmin\Twig\UrlExtension;
|
||||
use PhpMyAdmin\Twig\UtilExtension;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Twig\Cache\CacheInterface;
|
||||
use Twig\Environment;
|
||||
use Twig\Loader\FilesystemLoader;
|
||||
use function fclose;
|
||||
use function fopen;
|
||||
use function fwrite;
|
||||
use function json_encode;
|
||||
use function str_replace;
|
||||
use function strpos;
|
||||
use function is_file;
|
||||
use function sprintf;
|
||||
|
||||
final class CacheWarmupCommand extends Command
|
||||
{
|
||||
/** @var string */
|
||||
protected static $defaultName = 'cache:warmup';
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->setDescription('Warms up the Twig templates cache');
|
||||
$this->addOption('twig', null, null, 'Warm up twig templates cache.');
|
||||
$this->addOption('routing', null, null, 'Warm up routing cache.');
|
||||
$this->setHelp('The <info>%command.name%</info> command warms up the cache of the Twig templates.');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
if ($input->getOption('twig') === true && $input->getOption('routing') === true) {
|
||||
$output->writeln('Please specify --twig or --routing');
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ($input->getOption('twig') === true) {
|
||||
return $this->warmUpTwigCache($output);
|
||||
}
|
||||
|
||||
if ($input->getOption('routing') === true) {
|
||||
return $this->warmUpRoutingCache($output);
|
||||
}
|
||||
|
||||
$output->writeln('Warming up all caches.', OutputInterface::VERBOSITY_VERBOSE);
|
||||
$twigCode = $this->warmUptwigCache($output);
|
||||
if ($twigCode !== 0) {
|
||||
$output->writeln('Twig cache generation had an error.');
|
||||
|
||||
return $twigCode;
|
||||
}
|
||||
$routingCode = $this->warmUpTwigCache($output);
|
||||
if ($routingCode !== 0) {
|
||||
$output->writeln('Routing cache generation had an error.');
|
||||
|
||||
return $twigCode;
|
||||
}
|
||||
$output->writeln('Warm up of all caches done.', OutputInterface::VERBOSITY_VERBOSE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function warmUpRoutingCache(OutputInterface $output): int
|
||||
{
|
||||
$output->writeln('Warming up the routing cache', OutputInterface::VERBOSITY_VERBOSE);
|
||||
Routing::getDispatcher();
|
||||
|
||||
if (is_file(Routing::ROUTES_CACHE_FILE)) {
|
||||
$output->writeln('Warm up done.', OutputInterface::VERBOSITY_VERBOSE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
$output->writeln(
|
||||
sprintf(
|
||||
'Warm up did not work, the folder "%s" is probably not writable.',
|
||||
CACHE_DIR
|
||||
),
|
||||
OutputInterface::VERBOSITY_NORMAL
|
||||
);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
private function warmUpTwigCache(OutputInterface $output): int
|
||||
{
|
||||
global $cfg, $PMA_Config, $dbi;
|
||||
|
||||
$output->writeln('Warming up the twig cache', OutputInterface::VERBOSITY_VERBOSE);
|
||||
$cfg['environment'] = 'production';
|
||||
$PMA_Config = new Config(CONFIG_FILE);
|
||||
$PMA_Config->set('environment', $cfg['environment']);
|
||||
$dbi = new DatabaseInterface(new DbiDummy());
|
||||
$tplDir = ROOT_PATH . 'templates';
|
||||
$tmpDir = ROOT_PATH . 'twig-templates';
|
||||
|
||||
$loader = new FilesystemLoader($tplDir);
|
||||
$twig = new Environment($loader, [
|
||||
'auto_reload' => true,
|
||||
'cache' => $tmpDir,
|
||||
]);
|
||||
$twig->setExtensions([
|
||||
new CoreExtension(),
|
||||
new I18nExtension(),
|
||||
new MessageExtension(),
|
||||
new PluginsExtension(),
|
||||
new RelationExtension(),
|
||||
new SanitizeExtension(),
|
||||
new TableExtension(),
|
||||
new TrackerExtension(),
|
||||
new TransformationsExtension(),
|
||||
new UrlExtension(),
|
||||
new UtilExtension(),
|
||||
]);
|
||||
|
||||
/** @var CacheInterface $twigCache */
|
||||
$twigCache = $twig->getCache(false);
|
||||
|
||||
$output->writeln('Searching for files...', OutputInterface::VERBOSITY_VERY_VERBOSE);
|
||||
|
||||
$replacements = [];
|
||||
$templates = new RecursiveIteratorIterator(
|
||||
new RecursiveDirectoryIterator($tplDir),
|
||||
RecursiveIteratorIterator::LEAVES_ONLY
|
||||
);
|
||||
|
||||
$output->writeln('Warming templates', OutputInterface::VERBOSITY_VERY_VERBOSE);
|
||||
foreach ($templates as $file) {
|
||||
// Skip test files
|
||||
if (strpos($file->getPathname(), '/test/') !== false) {
|
||||
continue;
|
||||
}
|
||||
// force compilation
|
||||
if (! $file->isFile() || $file->getExtension() !== 'twig') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$name = str_replace($tplDir . '/', '', $file->getPathname());
|
||||
$output->writeln('Loading: ' . $name, OutputInterface::VERBOSITY_DEBUG);
|
||||
if (Environment::MAJOR_VERSION === 3) {
|
||||
$template = $twig->loadTemplate($twig->getTemplateClass($name), $name);
|
||||
} else {// @phpstan-ignore-line Twig 2
|
||||
$template = $twig->loadTemplate($name);// @phpstan-ignore-line Twig 2
|
||||
}
|
||||
|
||||
// Generate line map
|
||||
$cacheFilename = $twigCache->generateKey($name, $twig->getTemplateClass($name));
|
||||
$template_file = 'templates/' . $name;
|
||||
$cache_file = str_replace($tmpDir, 'twig-templates', $cacheFilename);
|
||||
$replacements[$cache_file] = [$template_file, $template->getDebugInfo()];
|
||||
}
|
||||
|
||||
$output->writeln('Writing replacements...', OutputInterface::VERBOSITY_VERY_VERBOSE);
|
||||
// Store replacements in JSON
|
||||
$handle = fopen($tmpDir . '/replace.json', 'w');
|
||||
if ($handle === false) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
fwrite($handle, (string) json_encode($replacements));
|
||||
fclose($handle);
|
||||
$output->writeln('Warm up done.', OutputInterface::VERBOSITY_VERBOSE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
102
admin/phpMyAdmin/libraries/classes/Command/SetVersionCommand.php
Normal file
102
admin/phpMyAdmin/libraries/classes/Command/SetVersionCommand.php
Normal file
|
@ -0,0 +1,102 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Command;
|
||||
|
||||
use RangeException;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use function file_put_contents;
|
||||
use function preg_match;
|
||||
use function sprintf;
|
||||
|
||||
final class SetVersionCommand extends Command
|
||||
{
|
||||
/** @var string */
|
||||
protected static $defaultName = 'set-version';
|
||||
|
||||
/** @var string */
|
||||
private static $generatedClassTemplate = <<<'PHP'
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin;
|
||||
|
||||
/**
|
||||
* This class is generated by scripts/console.
|
||||
*
|
||||
* @see \PhpMyAdmin\Command\SetVersionCommand
|
||||
*/
|
||||
final class Version
|
||||
{
|
||||
// The VERSION_SUFFIX constant is defined at libraries/vendor_config.php
|
||||
public const VERSION = '%1$u.%2$u.%3$u%4$s' . VERSION_SUFFIX;
|
||||
public const SERIES = '%1$u.%2$u';
|
||||
public const MAJOR = %1$u;
|
||||
public const MINOR = %2$u;
|
||||
public const PATCH = %3$u;
|
||||
public const ID = %1$u%2$02u%3$02u;
|
||||
public const PRE_RELEASE_NAME = '%5$s';
|
||||
public const IS_DEV = %6$s;
|
||||
}
|
||||
|
||||
PHP;
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->setDescription('Sets the version number');
|
||||
$this->setHelp('This command generates the PhpMyAdmin\Version class based on the version number provided.');
|
||||
$this->addArgument('version', InputArgument::REQUIRED, 'The version number');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
/** @var string $version */
|
||||
$version = $input->getArgument('version');
|
||||
|
||||
$generatedClass = $this->getGeneratedClass($version);
|
||||
|
||||
if (! $this->writeGeneratedClassFile($generatedClass)) {
|
||||
// failure
|
||||
return 1;
|
||||
}
|
||||
|
||||
$output->writeln('PhpMyAdmin\Version class successfully generated!');
|
||||
|
||||
// success
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function getGeneratedClass(string $version): string
|
||||
{
|
||||
// Do not allow any major below 5
|
||||
$return = preg_match('/^([5-9]+)\.(\d{1,2})\.(\d{1,2})(-([a-z0-9]+))?$/', $version, $matches);
|
||||
if ($return === false || $return === 0) {
|
||||
throw new RangeException('The version number is in the wrong format: ' . $version);
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
self::$generatedClassTemplate,
|
||||
$matches[1],
|
||||
$matches[2],
|
||||
$matches[3],
|
||||
$matches[4] ?? '',
|
||||
$matches[5] ?? '',
|
||||
($matches[5] ?? '') === 'dev' ? 'true' : 'false'
|
||||
);
|
||||
}
|
||||
|
||||
private function writeGeneratedClassFile(string $generatedClass): bool
|
||||
{
|
||||
$result = file_put_contents(
|
||||
ROOT_PATH . 'libraries/classes/Version.php',
|
||||
$generatedClass
|
||||
);
|
||||
|
||||
return $result !== false;
|
||||
}
|
||||
}
|
1536
admin/phpMyAdmin/libraries/classes/Config.php
Normal file
1536
admin/phpMyAdmin/libraries/classes/Config.php
Normal file
File diff suppressed because it is too large
Load diff
577
admin/phpMyAdmin/libraries/classes/Config/ConfigFile.php
Normal file
577
admin/phpMyAdmin/libraries/classes/Config/ConfigFile.php
Normal file
|
@ -0,0 +1,577 @@
|
|||
<?php
|
||||
/**
|
||||
* Config file management
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config;
|
||||
|
||||
use PhpMyAdmin\Core;
|
||||
use function array_diff;
|
||||
use function array_flip;
|
||||
use function array_keys;
|
||||
use function array_walk;
|
||||
use function count;
|
||||
use function is_array;
|
||||
use function preg_replace;
|
||||
|
||||
/**
|
||||
* Config file management class.
|
||||
* Stores its data in $_SESSION
|
||||
*/
|
||||
class ConfigFile
|
||||
{
|
||||
/**
|
||||
* Stores default PMA config from config.default.php
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $defaultCfg;
|
||||
|
||||
/**
|
||||
* Stores allowed values for non-standard fields
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $cfgDb;
|
||||
|
||||
/**
|
||||
* Stores original PMA config, not modified by user preferences
|
||||
*
|
||||
* @var array|null
|
||||
*/
|
||||
private $baseCfg;
|
||||
|
||||
/**
|
||||
* Whether we are currently working in PMA Setup context
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $isInSetup;
|
||||
|
||||
/**
|
||||
* Keys which will be always written to config file
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $persistKeys = [];
|
||||
|
||||
/**
|
||||
* Changes keys while updating config in {@link updateWithGlobalConfig()}
|
||||
* or reading by {@link getConfig()} or {@link getConfigArray()}
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $cfgUpdateReadMapping = [];
|
||||
|
||||
/**
|
||||
* Key filter for {@link set()}
|
||||
*
|
||||
* @var array|null
|
||||
*/
|
||||
private $setFilter;
|
||||
|
||||
/**
|
||||
* Instance id (key in $_SESSION array, separate for each server -
|
||||
* ConfigFile{server id})
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* Result for {@link flattenArray()}
|
||||
*
|
||||
* @var array|null
|
||||
*/
|
||||
private $flattenArrayResult;
|
||||
|
||||
/**
|
||||
* @param array|null $baseConfig base configuration read from
|
||||
* {@link PhpMyAdmin\Config::$base_config},
|
||||
* use only when not in PMA Setup
|
||||
*/
|
||||
public function __construct($baseConfig = null)
|
||||
{
|
||||
// load default config values
|
||||
$cfg = &$this->defaultCfg;
|
||||
include ROOT_PATH . 'libraries/config.default.php';
|
||||
|
||||
// load additional config information
|
||||
$this->cfgDb = include ROOT_PATH . 'libraries/config.values.php';
|
||||
|
||||
// apply default values overrides
|
||||
if (count($this->cfgDb['_overrides'])) {
|
||||
foreach ($this->cfgDb['_overrides'] as $path => $value) {
|
||||
Core::arrayWrite($path, $cfg, $value);
|
||||
}
|
||||
}
|
||||
|
||||
$this->baseCfg = $baseConfig;
|
||||
$this->isInSetup = $baseConfig === null;
|
||||
$this->id = 'ConfigFile' . $GLOBALS['server'];
|
||||
if (isset($_SESSION[$this->id])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$_SESSION[$this->id] = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets names of config options which will be placed in config file even if
|
||||
* they are set to their default values (use only full paths)
|
||||
*
|
||||
* @param array $keys the names of the config options
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setPersistKeys(array $keys)
|
||||
{
|
||||
// checking key presence is much faster than searching so move values
|
||||
// to keys
|
||||
$this->persistKeys = array_flip($keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns flipped array set by {@link setPersistKeys()}
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getPersistKeysMap()
|
||||
{
|
||||
return $this->persistKeys;
|
||||
}
|
||||
|
||||
/**
|
||||
* By default ConfigFile allows setting of all configuration keys, use
|
||||
* this method to set up a filter on {@link set()} method
|
||||
*
|
||||
* @param array|null $keys array of allowed keys or null to remove filter
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setAllowedKeys($keys)
|
||||
{
|
||||
if ($keys === null) {
|
||||
$this->setFilter = null;
|
||||
|
||||
return;
|
||||
}
|
||||
// checking key presence is much faster than searching so move values
|
||||
// to keys
|
||||
$this->setFilter = array_flip($keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets path mapping for updating config in
|
||||
* {@link updateWithGlobalConfig()} or reading
|
||||
* by {@link getConfig()} or {@link getConfigArray()}
|
||||
*
|
||||
* @param array $mapping Contains the mapping of "Server/config options"
|
||||
* to "Server/1/config options"
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setCfgUpdateReadMapping(array $mapping)
|
||||
{
|
||||
$this->cfgUpdateReadMapping = $mapping;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets configuration data
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function resetConfigData()
|
||||
{
|
||||
$_SESSION[$this->id] = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets configuration data (overrides old data)
|
||||
*
|
||||
* @param array $cfg Configuration options
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setConfigData(array $cfg)
|
||||
{
|
||||
$_SESSION[$this->id] = $cfg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets config value
|
||||
*
|
||||
* @param string $path Path
|
||||
* @param mixed $value Value
|
||||
* @param string $canonicalPath Canonical path
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set($path, $value, $canonicalPath = null)
|
||||
{
|
||||
if ($canonicalPath === null) {
|
||||
$canonicalPath = $this->getCanonicalPath($path);
|
||||
}
|
||||
|
||||
if ($this->setFilter !== null
|
||||
&& ! isset($this->setFilter[$canonicalPath])
|
||||
) {
|
||||
return;
|
||||
}
|
||||
// if the path isn't protected it may be removed
|
||||
if (isset($this->persistKeys[$canonicalPath])) {
|
||||
Core::arrayWrite($path, $_SESSION[$this->id], $value);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$defaultValue = $this->getDefault($canonicalPath);
|
||||
$removePath = $value === $defaultValue;
|
||||
if ($this->isInSetup) {
|
||||
// remove if it has a default value or is empty
|
||||
$removePath = $removePath
|
||||
|| (empty($value) && empty($defaultValue));
|
||||
} else {
|
||||
// get original config values not overwritten by user
|
||||
// preferences to allow for overwriting options set in
|
||||
// config.inc.php with default values
|
||||
$instanceDefaultValue = Core::arrayRead(
|
||||
$canonicalPath,
|
||||
$this->baseCfg
|
||||
);
|
||||
// remove if it has a default value and base config (config.inc.php)
|
||||
// uses default value
|
||||
$removePath = $removePath
|
||||
&& ($instanceDefaultValue === $defaultValue);
|
||||
}
|
||||
if ($removePath) {
|
||||
Core::arrayRemove($path, $_SESSION[$this->id]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Core::arrayWrite($path, $_SESSION[$this->id], $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flattens multidimensional array, changes indices to paths
|
||||
* (eg. 'key/subkey').
|
||||
* Used as array_walk() callback.
|
||||
*
|
||||
* @param mixed $value Value
|
||||
* @param mixed $key Key
|
||||
* @param mixed $prefix Prefix
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function flattenArray($value, $key, $prefix)
|
||||
{
|
||||
// no recursion for numeric arrays
|
||||
if (is_array($value) && ! isset($value[0])) {
|
||||
$prefix .= $key . '/';
|
||||
array_walk(
|
||||
$value,
|
||||
function ($value, $key, $prefix) {
|
||||
$this->flattenArray($value, $key, $prefix);
|
||||
},
|
||||
$prefix
|
||||
);
|
||||
} else {
|
||||
$this->flattenArrayResult[$prefix . $key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns default config in a flattened array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getFlatDefaultConfig()
|
||||
{
|
||||
$this->flattenArrayResult = [];
|
||||
array_walk(
|
||||
$this->defaultCfg,
|
||||
function ($value, $key, $prefix) {
|
||||
$this->flattenArray($value, $key, $prefix);
|
||||
},
|
||||
''
|
||||
);
|
||||
$flatConfig = $this->flattenArrayResult;
|
||||
$this->flattenArrayResult = null;
|
||||
|
||||
return $flatConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates config with values read from given array
|
||||
* (config will contain differences to defaults from config.defaults.php).
|
||||
*
|
||||
* @param array $cfg Configuration
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function updateWithGlobalConfig(array $cfg)
|
||||
{
|
||||
// load config array and flatten it
|
||||
$this->flattenArrayResult = [];
|
||||
array_walk(
|
||||
$cfg,
|
||||
function ($value, $key, $prefix) {
|
||||
$this->flattenArray($value, $key, $prefix);
|
||||
},
|
||||
''
|
||||
);
|
||||
$flatConfig = $this->flattenArrayResult;
|
||||
$this->flattenArrayResult = null;
|
||||
|
||||
// save values map for translating a few user preferences paths,
|
||||
// should be complemented by code reading from generated config
|
||||
// to perform inverse mapping
|
||||
foreach ($flatConfig as $path => $value) {
|
||||
if (isset($this->cfgUpdateReadMapping[$path])) {
|
||||
$path = $this->cfgUpdateReadMapping[$path];
|
||||
}
|
||||
$this->set($path, $value, $path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns config value or $default if it's not set
|
||||
*
|
||||
* @param string $path Path of config file
|
||||
* @param mixed $default Default values
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get($path, $default = null)
|
||||
{
|
||||
return Core::arrayRead($path, $_SESSION[$this->id], $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns default config value or $default it it's not set ie. it doesn't
|
||||
* exist in config.default.php ($cfg) and config.values.php
|
||||
* ($_cfg_db['_overrides'])
|
||||
*
|
||||
* @param string $canonicalPath Canonical path
|
||||
* @param mixed $default Default value
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getDefault($canonicalPath, $default = null)
|
||||
{
|
||||
return Core::arrayRead($canonicalPath, $this->defaultCfg, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns config value, if it's not set uses the default one; returns
|
||||
* $default if the path isn't set and doesn't contain a default value
|
||||
*
|
||||
* @param string $path Path
|
||||
* @param mixed $default Default value
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getValue($path, $default = null)
|
||||
{
|
||||
$v = Core::arrayRead($path, $_SESSION[$this->id], null);
|
||||
if ($v !== null) {
|
||||
return $v;
|
||||
}
|
||||
$path = $this->getCanonicalPath($path);
|
||||
|
||||
return $this->getDefault($path, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns canonical path
|
||||
*
|
||||
* @param string $path Path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCanonicalPath($path)
|
||||
{
|
||||
return preg_replace('#^Servers/([\d]+)/#', 'Servers/1/', $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns config database entry for $path
|
||||
*
|
||||
* @param string $path path of the variable in config db
|
||||
* @param mixed $default default value
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getDbEntry($path, $default = null)
|
||||
{
|
||||
return Core::arrayRead($path, $this->cfgDb, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns server count
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getServerCount()
|
||||
{
|
||||
return isset($_SESSION[$this->id]['Servers'])
|
||||
? count($_SESSION[$this->id]['Servers'])
|
||||
: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns server list
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
public function getServers()
|
||||
{
|
||||
return $_SESSION[$this->id]['Servers'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns DSN of given server
|
||||
*
|
||||
* @param int $server server index
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getServerDSN($server)
|
||||
{
|
||||
if (! isset($_SESSION[$this->id]['Servers'][$server])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$path = 'Servers/' . $server;
|
||||
$dsn = 'mysqli://';
|
||||
if ($this->getValue($path . '/auth_type') === 'config') {
|
||||
$dsn .= $this->getValue($path . '/user');
|
||||
if (! empty($this->getValue($path . '/password'))) {
|
||||
$dsn .= ':***';
|
||||
}
|
||||
$dsn .= '@';
|
||||
}
|
||||
if ($this->getValue($path . '/host') !== 'localhost') {
|
||||
$dsn .= $this->getValue($path . '/host');
|
||||
$port = $this->getValue($path . '/port');
|
||||
if ($port) {
|
||||
$dsn .= ':' . $port;
|
||||
}
|
||||
} else {
|
||||
$dsn .= $this->getValue($path . '/socket');
|
||||
}
|
||||
|
||||
return $dsn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns server name
|
||||
*
|
||||
* @param int $id server index
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getServerName($id)
|
||||
{
|
||||
if (! isset($_SESSION[$this->id]['Servers'][$id])) {
|
||||
return '';
|
||||
}
|
||||
$verbose = $this->get('Servers/' . $id . '/verbose');
|
||||
if (! empty($verbose)) {
|
||||
return $verbose;
|
||||
}
|
||||
$host = $this->get('Servers/' . $id . '/host');
|
||||
|
||||
return empty($host) ? 'localhost' : $host;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes server
|
||||
*
|
||||
* @param int $server server index
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function removeServer($server)
|
||||
{
|
||||
if (! isset($_SESSION[$this->id]['Servers'][$server])) {
|
||||
return;
|
||||
}
|
||||
$lastServer = $this->getServerCount();
|
||||
|
||||
for ($i = $server; $i < $lastServer; $i++) {
|
||||
$_SESSION[$this->id]['Servers'][$i]
|
||||
= $_SESSION[$this->id]['Servers'][$i + 1];
|
||||
}
|
||||
unset($_SESSION[$this->id]['Servers'][$lastServer]);
|
||||
|
||||
if (! isset($_SESSION[$this->id]['ServerDefault'])
|
||||
|| $_SESSION[$this->id]['ServerDefault'] != $lastServer
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
unset($_SESSION[$this->id]['ServerDefault']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns configuration array (full, multidimensional format)
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getConfig()
|
||||
{
|
||||
$c = $_SESSION[$this->id];
|
||||
foreach ($this->cfgUpdateReadMapping as $mapTo => $mapFrom) {
|
||||
// if the key $c exists in $map_to
|
||||
if (Core::arrayRead($mapTo, $c) === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Core::arrayWrite($mapTo, $c, Core::arrayRead($mapFrom, $c));
|
||||
Core::arrayRemove($mapFrom, $c);
|
||||
}
|
||||
|
||||
return $c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns configuration array (flat format)
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getConfigArray()
|
||||
{
|
||||
$this->flattenArrayResult = [];
|
||||
array_walk(
|
||||
$_SESSION[$this->id],
|
||||
function ($value, $key, $prefix) {
|
||||
$this->flattenArray($value, $key, $prefix);
|
||||
},
|
||||
''
|
||||
);
|
||||
$c = $this->flattenArrayResult;
|
||||
$this->flattenArrayResult = null;
|
||||
|
||||
$persistKeys = array_diff(
|
||||
array_keys($this->persistKeys),
|
||||
array_keys($c)
|
||||
);
|
||||
foreach ($persistKeys as $k) {
|
||||
$c[$k] = $this->getDefault($this->getCanonicalPath($k));
|
||||
}
|
||||
|
||||
foreach ($this->cfgUpdateReadMapping as $mapTo => $mapFrom) {
|
||||
if (! isset($c[$mapFrom])) {
|
||||
continue;
|
||||
}
|
||||
$c[$mapTo] = $c[$mapFrom];
|
||||
unset($c[$mapFrom]);
|
||||
}
|
||||
|
||||
return $c;
|
||||
}
|
||||
}
|
1024
admin/phpMyAdmin/libraries/classes/Config/Descriptions.php
Normal file
1024
admin/phpMyAdmin/libraries/classes/Config/Descriptions.php
Normal file
File diff suppressed because it is too large
Load diff
310
admin/phpMyAdmin/libraries/classes/Config/Form.php
Normal file
310
admin/phpMyAdmin/libraries/classes/Config/Form.php
Normal file
|
@ -0,0 +1,310 @@
|
|||
<?php
|
||||
/**
|
||||
* Form handling code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config;
|
||||
|
||||
use const E_USER_ERROR;
|
||||
use function array_combine;
|
||||
use function array_shift;
|
||||
use function array_walk;
|
||||
use function count;
|
||||
use function gettype;
|
||||
use function is_array;
|
||||
use function is_bool;
|
||||
use function is_int;
|
||||
use function is_string;
|
||||
use function ltrim;
|
||||
use function mb_strpos;
|
||||
use function mb_strrpos;
|
||||
use function mb_substr;
|
||||
use function str_replace;
|
||||
use function trigger_error;
|
||||
|
||||
/**
|
||||
* Base class for forms, loads default configuration options, checks allowed
|
||||
* values etc.
|
||||
*/
|
||||
class Form
|
||||
{
|
||||
/**
|
||||
* Form name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* Arbitrary index, doesn't affect class' behavior
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $index;
|
||||
|
||||
/**
|
||||
* Form fields (paths), filled by {@link readFormPaths()}, indexed by field name
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $fields;
|
||||
|
||||
/**
|
||||
* Stores default values for some fields (eg. pmadb tables)
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $default;
|
||||
|
||||
/**
|
||||
* Caches field types, indexed by field names
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $fieldsTypes;
|
||||
|
||||
/**
|
||||
* ConfigFile instance
|
||||
*
|
||||
* @var ConfigFile
|
||||
*/
|
||||
private $configFile;
|
||||
|
||||
/**
|
||||
* A counter for the number of groups
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private static $groupCounter = 0;
|
||||
|
||||
/**
|
||||
* Reads default config values
|
||||
*
|
||||
* @param string $formName Form name
|
||||
* @param array $form Form data
|
||||
* @param ConfigFile $cf Config file instance
|
||||
* @param int $index arbitrary index, stored in Form::$index
|
||||
*/
|
||||
public function __construct(
|
||||
$formName,
|
||||
array $form,
|
||||
ConfigFile $cf,
|
||||
$index = null
|
||||
) {
|
||||
$this->index = $index;
|
||||
$this->configFile = $cf;
|
||||
$this->loadForm($formName, $form);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns type of given option
|
||||
*
|
||||
* @param string $optionName path or field name
|
||||
*
|
||||
* @return string|null one of: boolean, integer, double, string, select, array
|
||||
*/
|
||||
public function getOptionType($optionName)
|
||||
{
|
||||
$key = ltrim(
|
||||
mb_substr(
|
||||
$optionName,
|
||||
(int) mb_strrpos($optionName, '/')
|
||||
),
|
||||
'/'
|
||||
);
|
||||
|
||||
return $this->fieldsTypes[$key] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns allowed values for select fields
|
||||
*
|
||||
* @param string $optionPath Option path
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getOptionValueList($optionPath)
|
||||
{
|
||||
$value = $this->configFile->getDbEntry($optionPath);
|
||||
if ($value === null) {
|
||||
trigger_error($optionPath . ' - select options not defined', E_USER_ERROR);
|
||||
|
||||
return [];
|
||||
}
|
||||
if (! is_array($value)) {
|
||||
trigger_error($optionPath . ' - not a static value list', E_USER_ERROR);
|
||||
|
||||
return [];
|
||||
}
|
||||
// convert array('#', 'a', 'b') to array('a', 'b')
|
||||
if (isset($value[0]) && $value[0] === '#') {
|
||||
// remove first element ('#')
|
||||
array_shift($value);
|
||||
|
||||
// $value has keys and value names, return it
|
||||
return $value;
|
||||
}
|
||||
|
||||
// convert value list array('a', 'b') to array('a' => 'a', 'b' => 'b')
|
||||
$hasStringKeys = false;
|
||||
$keys = [];
|
||||
for ($i = 0, $nb = count($value); $i < $nb; $i++) {
|
||||
if (! isset($value[$i])) {
|
||||
$hasStringKeys = true;
|
||||
break;
|
||||
}
|
||||
$keys[] = is_bool($value[$i]) ? (int) $value[$i] : $value[$i];
|
||||
}
|
||||
if (! $hasStringKeys) {
|
||||
$value = array_combine($keys, $value);
|
||||
}
|
||||
|
||||
// $value has keys and value names, return it
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* array_walk callback function, reads path of form fields from
|
||||
* array (see docs for \PhpMyAdmin\Config\Forms\BaseForm::getForms)
|
||||
*
|
||||
* @param mixed $value Value
|
||||
* @param mixed $key Key
|
||||
* @param mixed $prefix Prefix
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function readFormPathsCallback($value, $key, $prefix)
|
||||
{
|
||||
if (is_array($value)) {
|
||||
$prefix .= $key . '/';
|
||||
array_walk(
|
||||
$value,
|
||||
function ($value, $key, $prefix) {
|
||||
$this->readFormPathsCallback($value, $key, $prefix);
|
||||
},
|
||||
$prefix
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (! is_int($key)) {
|
||||
$this->default[$prefix . $key] = $value;
|
||||
$value = $key;
|
||||
}
|
||||
// add unique id to group ends
|
||||
if ($value === ':group:end') {
|
||||
$value .= ':' . self::$groupCounter++;
|
||||
}
|
||||
$this->fields[] = $prefix . $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the group counter, function for testing purposes
|
||||
*/
|
||||
public static function resetGroupCounter(): void
|
||||
{
|
||||
self::$groupCounter = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads form paths to {@link $fields}
|
||||
*
|
||||
* @param array $form Form
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function readFormPaths(array $form)
|
||||
{
|
||||
// flatten form fields' paths and save them to $fields
|
||||
$this->fields = [];
|
||||
array_walk(
|
||||
$form,
|
||||
function ($value, $key, $prefix) {
|
||||
$this->readFormPathsCallback($value, $key, $prefix);
|
||||
},
|
||||
''
|
||||
);
|
||||
|
||||
// $this->fields is an array of the form: [0..n] => 'field path'
|
||||
// change numeric indexes to contain field names (last part of the path)
|
||||
$paths = $this->fields;
|
||||
$this->fields = [];
|
||||
foreach ($paths as $path) {
|
||||
$key = ltrim(
|
||||
mb_substr($path, (int) mb_strrpos($path, '/')),
|
||||
'/'
|
||||
);
|
||||
$this->fields[$key] = $path;
|
||||
}
|
||||
// now $this->fields is an array of the form: 'field name' => 'field path'
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads fields' types to $this->fieldsTypes
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function readTypes()
|
||||
{
|
||||
$cf = $this->configFile;
|
||||
foreach ($this->fields as $name => $path) {
|
||||
if (mb_strpos((string) $name, ':group:') === 0) {
|
||||
$this->fieldsTypes[$name] = 'group';
|
||||
continue;
|
||||
}
|
||||
$v = $cf->getDbEntry($path);
|
||||
if ($v !== null) {
|
||||
$type = is_array($v) ? 'select' : $v;
|
||||
} else {
|
||||
$type = gettype($cf->getDefault($path));
|
||||
}
|
||||
$this->fieldsTypes[$name] = $type;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove slashes from group names
|
||||
*
|
||||
* @see issue #15836
|
||||
*
|
||||
* @param array $form The form data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function cleanGroupPaths(array $form): array
|
||||
{
|
||||
foreach ($form as &$name) {
|
||||
if (! is_string($name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (mb_strpos($name, ':group:') !== 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$name = str_replace('/', '-', $name);
|
||||
}
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads form settings and prepares class to work with given subset of
|
||||
* config file
|
||||
*
|
||||
* @param string $formName Form name
|
||||
* @param array $form Form
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function loadForm($formName, array $form)
|
||||
{
|
||||
$this->name = $formName;
|
||||
$form = $this->cleanGroupPaths($form);
|
||||
$this->readFormPaths($form);
|
||||
$this->readTypes();
|
||||
}
|
||||
}
|
967
admin/phpMyAdmin/libraries/classes/Config/FormDisplay.php
Normal file
967
admin/phpMyAdmin/libraries/classes/Config/FormDisplay.php
Normal file
|
@ -0,0 +1,967 @@
|
|||
<?php
|
||||
/**
|
||||
* Form management class, displays and processes forms
|
||||
*
|
||||
* Explanation of used terms:
|
||||
* o work_path - original field path, eg. Servers/4/verbose
|
||||
* o system_path - work_path modified so that it points to the first server,
|
||||
* eg. Servers/1/verbose
|
||||
* o translated_path - work_path modified for HTML field name, a path with
|
||||
* slashes changed to hyphens, eg. Servers-4-verbose
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config;
|
||||
|
||||
use PhpMyAdmin\Config\Forms\User\UserFormList;
|
||||
use PhpMyAdmin\Html\MySQLDocumentation;
|
||||
use PhpMyAdmin\Sanitize;
|
||||
use PhpMyAdmin\Util;
|
||||
use const E_USER_WARNING;
|
||||
use function array_flip;
|
||||
use function array_keys;
|
||||
use function array_search;
|
||||
use function count;
|
||||
use function explode;
|
||||
use function function_exists;
|
||||
use function gettype;
|
||||
use function implode;
|
||||
use function is_array;
|
||||
use function is_bool;
|
||||
use function is_numeric;
|
||||
use function mb_substr;
|
||||
use function preg_match;
|
||||
use function settype;
|
||||
use function sprintf;
|
||||
use function str_replace;
|
||||
use function trigger_error;
|
||||
use function trim;
|
||||
|
||||
/**
|
||||
* Form management class, displays and processes forms
|
||||
*/
|
||||
class FormDisplay
|
||||
{
|
||||
/**
|
||||
* ConfigFile instance
|
||||
*
|
||||
* @var ConfigFile
|
||||
*/
|
||||
private $configFile;
|
||||
|
||||
/**
|
||||
* Form list
|
||||
*
|
||||
* @var Form[]
|
||||
*/
|
||||
private $forms = [];
|
||||
|
||||
/**
|
||||
* Stores validation errors, indexed by paths
|
||||
* [ Form_name ] is an array of form errors
|
||||
* [path] is a string storing error associated with single field
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $errors = [];
|
||||
|
||||
/**
|
||||
* Paths changed so that they can be used as HTML ids, indexed by paths
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $translatedPaths = [];
|
||||
|
||||
/**
|
||||
* Server paths change indexes so we define maps from current server
|
||||
* path to the first one, indexed by work path
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $systemPaths = [];
|
||||
|
||||
/**
|
||||
* Language strings which will be sent to Messages JS variable
|
||||
* Will be looked up in $GLOBALS: str{value} or strSetup{value}
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $jsLangStrings = [];
|
||||
|
||||
/**
|
||||
* Tells whether forms have been validated
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $isValidated = true;
|
||||
|
||||
/**
|
||||
* Dictionary with user preferences keys
|
||||
*
|
||||
* @var array|null
|
||||
*/
|
||||
private $userprefsKeys;
|
||||
|
||||
/**
|
||||
* Dictionary with disallowed user preferences keys
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $userprefsDisallow;
|
||||
|
||||
/** @var FormDisplayTemplate */
|
||||
private $formDisplayTemplate;
|
||||
|
||||
/**
|
||||
* @param ConfigFile $cf Config file instance
|
||||
*/
|
||||
public function __construct(ConfigFile $cf)
|
||||
{
|
||||
$this->formDisplayTemplate = new FormDisplayTemplate($GLOBALS['PMA_Config']);
|
||||
$this->jsLangStrings = [
|
||||
'error_nan_p' => __('Not a positive number!'),
|
||||
'error_nan_nneg' => __('Not a non-negative number!'),
|
||||
'error_incorrect_port' => __('Not a valid port number!'),
|
||||
'error_invalid_value' => __('Incorrect value!'),
|
||||
'error_value_lte' => __('Value must be less than or equal to %s!'),
|
||||
];
|
||||
$this->configFile = $cf;
|
||||
// initialize validators
|
||||
Validator::getValidators($this->configFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link ConfigFile} associated with this instance
|
||||
*
|
||||
* @return ConfigFile
|
||||
*/
|
||||
public function getConfigFile()
|
||||
{
|
||||
return $this->configFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers form in form manager
|
||||
*
|
||||
* @param string $formName Form name
|
||||
* @param array $form Form data
|
||||
* @param int $serverId 0 if new server, validation; >= 1 if editing a server
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function registerForm($formName, array $form, $serverId = null)
|
||||
{
|
||||
$this->forms[$formName] = new Form(
|
||||
$formName,
|
||||
$form,
|
||||
$this->configFile,
|
||||
$serverId
|
||||
);
|
||||
$this->isValidated = false;
|
||||
foreach ($this->forms[$formName]->fields as $path) {
|
||||
$workPath = $serverId === null
|
||||
? $path
|
||||
: str_replace('Servers/1/', 'Servers/' . $serverId . '/', $path);
|
||||
$this->systemPaths[$workPath] = $path;
|
||||
$this->translatedPaths[$workPath] = str_replace('/', '-', $workPath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes forms, returns true on successful save
|
||||
*
|
||||
* @param bool $allowPartialSave allows for partial form saving
|
||||
* on failed validation
|
||||
* @param bool $checkFormSubmit whether check for $_POST['submit_save']
|
||||
*
|
||||
* @return bool whether processing was successful
|
||||
*/
|
||||
public function process($allowPartialSave = true, $checkFormSubmit = true)
|
||||
{
|
||||
if ($checkFormSubmit && ! isset($_POST['submit_save'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// save forms
|
||||
if (count($this->forms) > 0) {
|
||||
return $this->save(array_keys($this->forms), $allowPartialSave);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs validation for all registered forms
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function validate()
|
||||
{
|
||||
if ($this->isValidated) {
|
||||
return;
|
||||
}
|
||||
|
||||
$paths = [];
|
||||
$values = [];
|
||||
foreach ($this->forms as $form) {
|
||||
/** @var Form $form */
|
||||
$paths[] = $form->name;
|
||||
// collect values and paths
|
||||
foreach ($form->fields as $path) {
|
||||
$workPath = array_search($path, $this->systemPaths);
|
||||
$values[$path] = $this->configFile->getValue($workPath);
|
||||
$paths[] = $path;
|
||||
}
|
||||
}
|
||||
|
||||
// run validation
|
||||
$errors = Validator::validate(
|
||||
$this->configFile,
|
||||
$paths,
|
||||
$values,
|
||||
false
|
||||
);
|
||||
|
||||
// change error keys from canonical paths to work paths
|
||||
if (is_array($errors) && count($errors) > 0) {
|
||||
$this->errors = [];
|
||||
foreach ($errors as $path => $errorList) {
|
||||
$workPath = array_search($path, $this->systemPaths);
|
||||
// field error
|
||||
if (! $workPath) {
|
||||
// form error, fix path
|
||||
$workPath = $path;
|
||||
}
|
||||
$this->errors[$workPath] = $errorList;
|
||||
}
|
||||
}
|
||||
$this->isValidated = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs HTML for the forms under the menu tab
|
||||
*
|
||||
* @param bool $showRestoreDefault whether to show "restore default"
|
||||
* button besides the input field
|
||||
* @param array $jsDefault stores JavaScript code
|
||||
* to be displayed
|
||||
* @param array $js will be updated with javascript code
|
||||
* @param bool $showButtons whether show submit and reset button
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function displayForms(
|
||||
$showRestoreDefault,
|
||||
array &$jsDefault,
|
||||
array &$js,
|
||||
$showButtons
|
||||
) {
|
||||
$htmlOutput = '';
|
||||
$validators = Validator::getValidators($this->configFile);
|
||||
|
||||
foreach ($this->forms as $form) {
|
||||
/** @var Form $form */
|
||||
$formErrors = $this->errors[$form->name] ?? null;
|
||||
$htmlOutput .= $this->formDisplayTemplate->displayFieldsetTop(
|
||||
Descriptions::get('Form_' . $form->name),
|
||||
Descriptions::get('Form_' . $form->name, 'desc'),
|
||||
$formErrors,
|
||||
['id' => $form->name]
|
||||
);
|
||||
|
||||
foreach ($form->fields as $field => $path) {
|
||||
$workPath = array_search($path, $this->systemPaths);
|
||||
$translatedPath = $this->translatedPaths[$workPath];
|
||||
// always true/false for user preferences display
|
||||
// otherwise null
|
||||
$userPrefsAllow = isset($this->userprefsKeys[$path])
|
||||
? ! isset($this->userprefsDisallow[$path])
|
||||
: null;
|
||||
// display input
|
||||
$htmlOutput .= $this->displayFieldInput(
|
||||
$form,
|
||||
$field,
|
||||
$path,
|
||||
$workPath,
|
||||
$translatedPath,
|
||||
$showRestoreDefault,
|
||||
$userPrefsAllow,
|
||||
$jsDefault
|
||||
);
|
||||
// register JS validators for this field
|
||||
if (! isset($validators[$path])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->formDisplayTemplate->addJsValidate($translatedPath, $validators[$path], $js);
|
||||
}
|
||||
$htmlOutput .= $this->formDisplayTemplate->displayFieldsetBottom($showButtons);
|
||||
}
|
||||
|
||||
return $htmlOutput;
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs HTML for forms
|
||||
*
|
||||
* @param bool $tabbedForm if true, use a form with tabs
|
||||
* @param bool $showRestoreDefault whether show "restore default" button
|
||||
* besides the input field
|
||||
* @param bool $showButtons whether show submit and reset button
|
||||
* @param string $formAction action attribute for the form
|
||||
* @param array|null $hiddenFields array of form hidden fields (key: field
|
||||
* name)
|
||||
*
|
||||
* @return string HTML for forms
|
||||
*/
|
||||
public function getDisplay(
|
||||
$tabbedForm = false,
|
||||
$showRestoreDefault = false,
|
||||
$showButtons = true,
|
||||
$formAction = null,
|
||||
$hiddenFields = null
|
||||
) {
|
||||
static $jsLangSent = false;
|
||||
|
||||
$htmlOutput = '';
|
||||
|
||||
$js = [];
|
||||
$jsDefault = [];
|
||||
|
||||
$htmlOutput .= $this->formDisplayTemplate->displayFormTop($formAction, 'post', $hiddenFields);
|
||||
|
||||
if ($tabbedForm) {
|
||||
$tabs = [];
|
||||
foreach ($this->forms as $form) {
|
||||
$tabs[$form->name] = Descriptions::get('Form_' . $form->name);
|
||||
}
|
||||
$htmlOutput .= $this->formDisplayTemplate->displayTabsTop($tabs);
|
||||
}
|
||||
|
||||
// validate only when we aren't displaying a "new server" form
|
||||
$isNewServer = false;
|
||||
foreach ($this->forms as $form) {
|
||||
/** @var Form $form */
|
||||
if ($form->index === 0) {
|
||||
$isNewServer = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (! $isNewServer) {
|
||||
$this->validate();
|
||||
}
|
||||
|
||||
// user preferences
|
||||
$this->loadUserprefsInfo();
|
||||
|
||||
// display forms
|
||||
$htmlOutput .= $this->displayForms(
|
||||
$showRestoreDefault,
|
||||
$jsDefault,
|
||||
$js,
|
||||
$showButtons
|
||||
);
|
||||
|
||||
if ($tabbedForm) {
|
||||
$htmlOutput .= $this->formDisplayTemplate->displayTabsBottom();
|
||||
}
|
||||
$htmlOutput .= $this->formDisplayTemplate->displayFormBottom();
|
||||
|
||||
// if not already done, send strings used for validation to JavaScript
|
||||
if (! $jsLangSent) {
|
||||
$jsLangSent = true;
|
||||
$jsLang = [];
|
||||
foreach ($this->jsLangStrings as $strName => $strValue) {
|
||||
$jsLang[] = "'" . $strName . "': '" . Sanitize::jsFormat($strValue, false) . '\'';
|
||||
}
|
||||
$js[] = "$.extend(Messages, {\n\t"
|
||||
. implode(",\n\t", $jsLang) . '})';
|
||||
}
|
||||
|
||||
$js[] = "$.extend(defaultValues, {\n\t"
|
||||
. implode(",\n\t", $jsDefault) . '})';
|
||||
|
||||
return $htmlOutput . $this->formDisplayTemplate->displayJavascript($js);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares data for input field display and outputs HTML code
|
||||
*
|
||||
* @param Form $form Form object
|
||||
* @param string $field field name as it appears in $form
|
||||
* @param string $systemPath field path, eg. Servers/1/verbose
|
||||
* @param string $workPath work path, eg. Servers/4/verbose
|
||||
* @param string $translatedPath work path changed so that it can be
|
||||
* used as XHTML id
|
||||
* @param bool $showRestoreDefault whether show "restore default" button
|
||||
* besides the input field
|
||||
* @param bool|null $userPrefsAllow whether user preferences are enabled
|
||||
* for this field (null - no support,
|
||||
* true/false - enabled/disabled)
|
||||
* @param array $jsDefault array which stores JavaScript code
|
||||
* to be displayed
|
||||
*
|
||||
* @return string|null HTML for input field
|
||||
*/
|
||||
private function displayFieldInput(
|
||||
Form $form,
|
||||
$field,
|
||||
$systemPath,
|
||||
$workPath,
|
||||
$translatedPath,
|
||||
$showRestoreDefault,
|
||||
$userPrefsAllow,
|
||||
array &$jsDefault
|
||||
) {
|
||||
$name = Descriptions::get($systemPath);
|
||||
$description = Descriptions::get($systemPath, 'desc');
|
||||
|
||||
$value = $this->configFile->get($workPath);
|
||||
$valueDefault = $this->configFile->getDefault($systemPath);
|
||||
$valueIsDefault = false;
|
||||
if ($value === null || $value === $valueDefault) {
|
||||
$value = $valueDefault;
|
||||
$valueIsDefault = true;
|
||||
}
|
||||
|
||||
$opts = [
|
||||
'doc' => $this->getDocLink($systemPath),
|
||||
'show_restore_default' => $showRestoreDefault,
|
||||
'userprefs_allow' => $userPrefsAllow,
|
||||
'userprefs_comment' => Descriptions::get($systemPath, 'cmt'),
|
||||
];
|
||||
if (isset($form->default[$systemPath])) {
|
||||
$opts['setvalue'] = (string) $form->default[$systemPath];
|
||||
}
|
||||
|
||||
if (isset($this->errors[$workPath])) {
|
||||
$opts['errors'] = $this->errors[$workPath];
|
||||
}
|
||||
|
||||
$type = '';
|
||||
switch ($form->getOptionType($field)) {
|
||||
case 'string':
|
||||
$type = 'text';
|
||||
break;
|
||||
case 'short_string':
|
||||
$type = 'short_text';
|
||||
break;
|
||||
case 'double':
|
||||
case 'integer':
|
||||
$type = 'number_text';
|
||||
break;
|
||||
case 'boolean':
|
||||
$type = 'checkbox';
|
||||
break;
|
||||
case 'select':
|
||||
$type = 'select';
|
||||
$opts['values'] = $form->getOptionValueList($form->fields[$field]);
|
||||
break;
|
||||
case 'array':
|
||||
$type = 'list';
|
||||
$value = (array) $value;
|
||||
$valueDefault = (array) $valueDefault;
|
||||
break;
|
||||
case 'group':
|
||||
// :group:end is changed to :group:end:{unique id} in Form class
|
||||
$htmlOutput = '';
|
||||
if (mb_substr($field, 7, 4) !== 'end:') {
|
||||
$htmlOutput .= $this->formDisplayTemplate->displayGroupHeader(
|
||||
mb_substr($field, 7)
|
||||
);
|
||||
} else {
|
||||
$this->formDisplayTemplate->displayGroupFooter();
|
||||
}
|
||||
|
||||
return $htmlOutput;
|
||||
case 'NULL':
|
||||
trigger_error('Field ' . $systemPath . ' has no type', E_USER_WARNING);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// detect password fields
|
||||
if ($type === 'text'
|
||||
&& (mb_substr($translatedPath, -9) === '-password'
|
||||
|| mb_substr($translatedPath, -4) === 'pass'
|
||||
|| mb_substr($translatedPath, -4) === 'Pass')
|
||||
) {
|
||||
$type = 'password';
|
||||
}
|
||||
|
||||
// TrustedProxies requires changes before displaying
|
||||
if ($systemPath === 'TrustedProxies') {
|
||||
foreach ($value as $ip => &$v) {
|
||||
if (preg_match('/^-\d+$/', $ip)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$v = $ip . ': ' . $v;
|
||||
}
|
||||
}
|
||||
$this->setComments($systemPath, $opts);
|
||||
|
||||
// send default value to form's JS
|
||||
$jsLine = '\'' . $translatedPath . '\': ';
|
||||
switch ($type) {
|
||||
case 'text':
|
||||
case 'short_text':
|
||||
case 'number_text':
|
||||
case 'password':
|
||||
$jsLine .= '\'' . Sanitize::escapeJsString($valueDefault) . '\'';
|
||||
break;
|
||||
case 'checkbox':
|
||||
$jsLine .= $valueDefault ? 'true' : 'false';
|
||||
break;
|
||||
case 'select':
|
||||
$valueDefaultJs = is_bool($valueDefault)
|
||||
? (int) $valueDefault
|
||||
: $valueDefault;
|
||||
$jsLine .= '[\'' . Sanitize::escapeJsString($valueDefaultJs) . '\']';
|
||||
break;
|
||||
case 'list':
|
||||
$val = $valueDefault;
|
||||
if (isset($val['wrapper_params'])) {
|
||||
unset($val['wrapper_params']);
|
||||
}
|
||||
$jsLine .= '\'' . Sanitize::escapeJsString(implode("\n", $val))
|
||||
. '\'';
|
||||
break;
|
||||
}
|
||||
$jsDefault[] = $jsLine;
|
||||
|
||||
return $this->formDisplayTemplate->displayInput(
|
||||
$translatedPath,
|
||||
$name,
|
||||
$type,
|
||||
$value,
|
||||
$description,
|
||||
$valueIsDefault,
|
||||
$opts
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays errors
|
||||
*
|
||||
* @return string|null HTML for errors
|
||||
*/
|
||||
public function displayErrors()
|
||||
{
|
||||
$this->validate();
|
||||
if (count($this->errors) === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$htmlOutput = '';
|
||||
|
||||
foreach ($this->errors as $systemPath => $errorList) {
|
||||
if (isset($this->systemPaths[$systemPath])) {
|
||||
$name = Descriptions::get($this->systemPaths[$systemPath]);
|
||||
} else {
|
||||
$name = Descriptions::get('Form_' . $systemPath);
|
||||
}
|
||||
$htmlOutput .= $this->formDisplayTemplate->displayErrors($name, $errorList);
|
||||
}
|
||||
|
||||
return $htmlOutput;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverts erroneous fields to their default values
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function fixErrors()
|
||||
{
|
||||
$this->validate();
|
||||
if (count($this->errors) === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$cf = $this->configFile;
|
||||
foreach (array_keys($this->errors) as $workPath) {
|
||||
if (! isset($this->systemPaths[$workPath])) {
|
||||
continue;
|
||||
}
|
||||
$canonicalPath = $this->systemPaths[$workPath];
|
||||
$cf->set($workPath, $cf->getDefault($canonicalPath));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates select field and casts $value to correct type
|
||||
*
|
||||
* @param string|bool $value Current value
|
||||
* @param array $allowed List of allowed values
|
||||
*/
|
||||
private function validateSelect(&$value, array $allowed): bool
|
||||
{
|
||||
$valueCmp = is_bool($value)
|
||||
? (int) $value
|
||||
: $value;
|
||||
foreach ($allowed as $vk => $v) {
|
||||
// equality comparison only if both values are numeric or not numeric
|
||||
// (allows to skip 0 == 'string' equalling to true)
|
||||
// or identity (for string-string)
|
||||
if (! (($vk == $value && ! (is_numeric($valueCmp) xor is_numeric($vk)))
|
||||
|| $vk === $value)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// keep boolean value as boolean
|
||||
if (! is_bool($value)) {
|
||||
// phpcs:ignore Generic.PHP.ForbiddenFunctions
|
||||
settype($value, gettype($vk));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and saves form data to session
|
||||
*
|
||||
* @param array|string $forms array of form names
|
||||
* @param bool $allowPartialSave allows for partial form saving on
|
||||
* failed validation
|
||||
*
|
||||
* @return bool true on success (no errors and all saved)
|
||||
*/
|
||||
public function save($forms, $allowPartialSave = true)
|
||||
{
|
||||
$result = true;
|
||||
$forms = (array) $forms;
|
||||
|
||||
$values = [];
|
||||
$toSave = [];
|
||||
$isSetupScript = $GLOBALS['PMA_Config']->get('is_setup');
|
||||
if ($isSetupScript) {
|
||||
$this->loadUserprefsInfo();
|
||||
}
|
||||
|
||||
$this->errors = [];
|
||||
foreach ($forms as $formName) {
|
||||
if (! isset($this->forms[$formName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/** @var Form $form */
|
||||
$form = $this->forms[$formName];
|
||||
// get current server id
|
||||
$changeIndex = $form->index === 0
|
||||
? $this->configFile->getServerCount() + 1
|
||||
: false;
|
||||
// grab POST values
|
||||
foreach ($form->fields as $field => $systemPath) {
|
||||
$workPath = array_search($systemPath, $this->systemPaths);
|
||||
$key = $this->translatedPaths[$workPath];
|
||||
$type = (string) $form->getOptionType($field);
|
||||
|
||||
// skip groups
|
||||
if ($type === 'group') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// ensure the value is set
|
||||
if (! isset($_POST[$key])) {
|
||||
// checkboxes aren't set by browsers if they're off
|
||||
if ($type !== 'boolean') {
|
||||
$this->errors[$form->name][] = sprintf(
|
||||
__('Missing data for %s'),
|
||||
'<i>' . Descriptions::get($systemPath) . '</i>'
|
||||
);
|
||||
$result = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
$_POST[$key] = false;
|
||||
}
|
||||
|
||||
// user preferences allow/disallow
|
||||
if ($isSetupScript
|
||||
&& isset($this->userprefsKeys[$systemPath])
|
||||
) {
|
||||
if (isset($this->userprefsDisallow[$systemPath], $_POST[$key . '-userprefs-allow'])
|
||||
) {
|
||||
unset($this->userprefsDisallow[$systemPath]);
|
||||
} elseif (! isset($_POST[$key . '-userprefs-allow'])) {
|
||||
$this->userprefsDisallow[$systemPath] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// cast variables to correct type
|
||||
switch ($type) {
|
||||
case 'double':
|
||||
$_POST[$key] = Util::requestString($_POST[$key]);
|
||||
// phpcs:ignore Generic.PHP.ForbiddenFunctions
|
||||
settype($_POST[$key], 'float');
|
||||
break;
|
||||
case 'boolean':
|
||||
case 'integer':
|
||||
if ($_POST[$key] !== '') {
|
||||
$_POST[$key] = Util::requestString($_POST[$key]);
|
||||
// phpcs:ignore Generic.PHP.ForbiddenFunctions
|
||||
settype($_POST[$key], $type);
|
||||
}
|
||||
break;
|
||||
case 'select':
|
||||
$successfullyValidated = $this->validateSelect(
|
||||
$_POST[$key],
|
||||
$form->getOptionValueList($systemPath)
|
||||
);
|
||||
if (! $successfullyValidated) {
|
||||
$this->errors[$workPath][] = __('Incorrect value!');
|
||||
$result = false;
|
||||
// "continue" for the $form->fields foreach-loop
|
||||
continue 2;
|
||||
}
|
||||
break;
|
||||
case 'string':
|
||||
case 'short_string':
|
||||
$_POST[$key] = Util::requestString($_POST[$key]);
|
||||
break;
|
||||
case 'array':
|
||||
// eliminate empty values and ensure we have an array
|
||||
$postValues = is_array($_POST[$key])
|
||||
? $_POST[$key]
|
||||
: explode("\n", $_POST[$key]);
|
||||
$_POST[$key] = [];
|
||||
$this->fillPostArrayParameters($postValues, $key);
|
||||
break;
|
||||
}
|
||||
|
||||
// now we have value with proper type
|
||||
$values[$systemPath] = $_POST[$key];
|
||||
if ($changeIndex !== false) {
|
||||
$workPath = str_replace(
|
||||
'Servers/' . $form->index . '/',
|
||||
'Servers/' . $changeIndex . '/',
|
||||
$workPath
|
||||
);
|
||||
}
|
||||
$toSave[$workPath] = $systemPath;
|
||||
}
|
||||
}
|
||||
|
||||
// save forms
|
||||
if (! $allowPartialSave && ! empty($this->errors)) {
|
||||
// don't look for non-critical errors
|
||||
$this->validate();
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
foreach ($toSave as $workPath => $path) {
|
||||
// TrustedProxies requires changes before saving
|
||||
if ($path === 'TrustedProxies') {
|
||||
$proxies = [];
|
||||
$i = 0;
|
||||
foreach ($values[$path] as $value) {
|
||||
$matches = [];
|
||||
$match = preg_match(
|
||||
'/^(.+):(?:[ ]?)(\\w+)$/',
|
||||
$value,
|
||||
$matches
|
||||
);
|
||||
if ($match) {
|
||||
// correct 'IP: HTTP header' pair
|
||||
$ip = trim($matches[1]);
|
||||
$proxies[$ip] = trim($matches[2]);
|
||||
} else {
|
||||
// save also incorrect values
|
||||
$proxies['-' . $i] = $value;
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
$values[$path] = $proxies;
|
||||
}
|
||||
$this->configFile->set($workPath, $values[$path], $path);
|
||||
}
|
||||
if ($isSetupScript) {
|
||||
$this->configFile->set(
|
||||
'UserprefsDisallow',
|
||||
array_keys($this->userprefsDisallow)
|
||||
);
|
||||
}
|
||||
|
||||
// don't look for non-critical errors
|
||||
$this->validate();
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells whether form validation failed
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasErrors()
|
||||
{
|
||||
return count($this->errors) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns link to documentation
|
||||
*
|
||||
* @param string $path Path to documentation
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDocLink($path)
|
||||
{
|
||||
$test = mb_substr($path, 0, 6);
|
||||
if ($test === 'Import' || $test === 'Export') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return MySQLDocumentation::getDocumentationLink(
|
||||
'config',
|
||||
'cfg_' . $this->getOptName($path),
|
||||
Sanitize::isSetup() ? '../' : './'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes path so it can be used in URLs
|
||||
*
|
||||
* @param string $path Path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getOptName($path)
|
||||
{
|
||||
return str_replace(['Servers/1/', '/'], ['Servers/', '_'], $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills out {@link userprefs_keys} and {@link userprefs_disallow}
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function loadUserprefsInfo()
|
||||
{
|
||||
if ($this->userprefsKeys !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->userprefsKeys = array_flip(UserFormList::getFields());
|
||||
// read real config for user preferences display
|
||||
$userPrefsDisallow = $GLOBALS['PMA_Config']->get('is_setup')
|
||||
? $this->configFile->get('UserprefsDisallow', [])
|
||||
: $GLOBALS['cfg']['UserprefsDisallow'];
|
||||
$this->userprefsDisallow = array_flip($userPrefsDisallow ?? []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets field comments and warnings based on current environment
|
||||
*
|
||||
* @param string $systemPath Path to settings
|
||||
* @param array $opts Chosen options
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function setComments($systemPath, array &$opts)
|
||||
{
|
||||
// RecodingEngine - mark unavailable types
|
||||
if ($systemPath === 'RecodingEngine') {
|
||||
$comment = '';
|
||||
if (! function_exists('iconv')) {
|
||||
$opts['values']['iconv'] .= ' (' . __('unavailable') . ')';
|
||||
$comment = sprintf(
|
||||
__('"%s" requires %s extension'),
|
||||
'iconv',
|
||||
'iconv'
|
||||
);
|
||||
}
|
||||
if (! function_exists('recode_string')) {
|
||||
$opts['values']['recode'] .= ' (' . __('unavailable') . ')';
|
||||
$comment .= ($comment ? ', ' : '') . sprintf(
|
||||
__('"%s" requires %s extension'),
|
||||
'recode',
|
||||
'recode'
|
||||
);
|
||||
}
|
||||
/* mbstring is always there thanks to polyfill */
|
||||
$opts['comment'] = $comment;
|
||||
$opts['comment_warning'] = true;
|
||||
}
|
||||
// ZipDump, GZipDump, BZipDump - check function availability
|
||||
if ($systemPath === 'ZipDump'
|
||||
|| $systemPath === 'GZipDump'
|
||||
|| $systemPath === 'BZipDump'
|
||||
) {
|
||||
$comment = '';
|
||||
$funcs = [
|
||||
'ZipDump' => [
|
||||
'zip_open',
|
||||
'gzcompress',
|
||||
],
|
||||
'GZipDump' => [
|
||||
'gzopen',
|
||||
'gzencode',
|
||||
],
|
||||
'BZipDump' => [
|
||||
'bzopen',
|
||||
'bzcompress',
|
||||
],
|
||||
];
|
||||
if (! function_exists($funcs[$systemPath][0])) {
|
||||
$comment = sprintf(
|
||||
__(
|
||||
'Compressed import will not work due to missing function %s.'
|
||||
),
|
||||
$funcs[$systemPath][0]
|
||||
);
|
||||
}
|
||||
if (! function_exists($funcs[$systemPath][1])) {
|
||||
$comment .= ($comment ? '; ' : '') . sprintf(
|
||||
__(
|
||||
'Compressed export will not work due to missing function %s.'
|
||||
),
|
||||
$funcs[$systemPath][1]
|
||||
);
|
||||
}
|
||||
$opts['comment'] = $comment;
|
||||
$opts['comment_warning'] = true;
|
||||
}
|
||||
if ($GLOBALS['PMA_Config']->get('is_setup')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($systemPath !== 'MaxDbList' && $systemPath !== 'MaxTableList'
|
||||
&& $systemPath !== 'QueryHistoryMax'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$opts['comment'] = sprintf(
|
||||
__('maximum %s'),
|
||||
$GLOBALS['cfg'][$systemPath]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy items of an array to $_POST variable
|
||||
*
|
||||
* @param array $postValues List of parameters
|
||||
* @param string $key Array key
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function fillPostArrayParameters(array $postValues, $key)
|
||||
{
|
||||
foreach ($postValues as $v) {
|
||||
$v = Util::requestString($v);
|
||||
if ($v === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$_POST[$key][] = $v;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,491 @@
|
|||
<?php
|
||||
/**
|
||||
* Form templates
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config;
|
||||
|
||||
use PhpMyAdmin\Config;
|
||||
use PhpMyAdmin\Html\Generator;
|
||||
use PhpMyAdmin\Sanitize;
|
||||
use PhpMyAdmin\Template;
|
||||
use function array_flip;
|
||||
use function array_merge;
|
||||
use function array_shift;
|
||||
use function defined;
|
||||
use function htmlspecialchars;
|
||||
use function htmlspecialchars_decode;
|
||||
use function implode;
|
||||
use function is_bool;
|
||||
use function mb_strtolower;
|
||||
use function sprintf;
|
||||
use function is_string;
|
||||
|
||||
/**
|
||||
* PhpMyAdmin\Config\FormDisplayTemplate class
|
||||
*/
|
||||
class FormDisplayTemplate
|
||||
{
|
||||
/** @var int */
|
||||
public $group;
|
||||
|
||||
/** @var Config */
|
||||
protected $config;
|
||||
|
||||
/** @var Template */
|
||||
public $template;
|
||||
|
||||
/**
|
||||
* @param Config $config Config instance
|
||||
*/
|
||||
public function __construct(Config $config)
|
||||
{
|
||||
$this->config = $config;
|
||||
$this->template = new Template();
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays top part of the form
|
||||
*
|
||||
* @param string $action default: $_SERVER['REQUEST_URI']
|
||||
* @param string $method 'post' or 'get'
|
||||
* @param array|null $hiddenFields array of form hidden fields (key: field name)
|
||||
*/
|
||||
public function displayFormTop(
|
||||
$action = null,
|
||||
$method = 'post',
|
||||
$hiddenFields = null
|
||||
): string {
|
||||
static $hasCheckPageRefresh = false;
|
||||
|
||||
if ($action === null) {
|
||||
$action = $_SERVER['REQUEST_URI'];
|
||||
}
|
||||
if ($method !== 'post') {
|
||||
$method = 'get';
|
||||
}
|
||||
|
||||
/**
|
||||
* We do validation on page refresh when browser remembers field values,
|
||||
* add a field with known value which will be used for checks.
|
||||
*/
|
||||
if (! $hasCheckPageRefresh) {
|
||||
$hasCheckPageRefresh = true;
|
||||
}
|
||||
|
||||
return $this->template->render('config/form_display/form_top', [
|
||||
'method' => $method,
|
||||
'action' => $action,
|
||||
'has_check_page_refresh' => $hasCheckPageRefresh,
|
||||
'hidden_fields' => (array) $hiddenFields,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays form tabs which are given by an array indexed by fieldset id
|
||||
* ({@link self::displayFieldsetTop}), with values being tab titles.
|
||||
*
|
||||
* @param array $tabs tab names
|
||||
*/
|
||||
public function displayTabsTop(array $tabs): string
|
||||
{
|
||||
return $this->template->render('config/form_display/tabs_top', ['tabs' => $tabs]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays top part of a fieldset
|
||||
*
|
||||
* @param string $title title of fieldset
|
||||
* @param string $description description shown on top of fieldset
|
||||
* @param array|null $errors error messages to display
|
||||
* @param array $attributes optional extra attributes of fieldset
|
||||
*/
|
||||
public function displayFieldsetTop(
|
||||
$title = '',
|
||||
$description = '',
|
||||
$errors = null,
|
||||
array $attributes = []
|
||||
): string {
|
||||
$this->group = 0;
|
||||
|
||||
$attributes = array_merge(['class' => 'optbox'], $attributes);
|
||||
|
||||
return $this->template->render('config/form_display/fieldset_top', [
|
||||
'attributes' => $attributes,
|
||||
'title' => $title,
|
||||
'description' => $description,
|
||||
'errors' => $errors,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays input field
|
||||
*
|
||||
* $opts keys:
|
||||
* o doc - (string) documentation link
|
||||
* o errors - error array
|
||||
* o setvalue - (string) shows button allowing to set predefined value
|
||||
* o show_restore_default - (boolean) whether show "restore default" button
|
||||
* o userprefs_allow - whether user preferences are enabled for this field
|
||||
* (null - no support, true/false - enabled/disabled)
|
||||
* o userprefs_comment - (string) field comment
|
||||
* o values - key - value pairs for <select> fields
|
||||
* o values_escaped - (boolean) tells whether values array is already escaped
|
||||
* (defaults to false)
|
||||
* o values_disabled - (array)list of disabled values (keys from values)
|
||||
* o comment - (string) tooltip comment
|
||||
* o comment_warning - (bool) whether this comments warns about something
|
||||
*
|
||||
* @param string $path config option path
|
||||
* @param string $name config option name
|
||||
* @param string $type type of config option
|
||||
* @param mixed $value current value
|
||||
* @param string $description verbose description
|
||||
* @param bool $valueIsDefault whether value is default
|
||||
* @param array|null $opts see above description
|
||||
*/
|
||||
public function displayInput(
|
||||
$path,
|
||||
$name,
|
||||
$type,
|
||||
$value,
|
||||
$description = '',
|
||||
$valueIsDefault = true,
|
||||
$opts = null
|
||||
): string {
|
||||
static $icons; // An array of IMG tags used further below in the function
|
||||
|
||||
if (defined('TESTSUITE')) {
|
||||
$icons = null;
|
||||
}
|
||||
|
||||
$isSetupScript = $this->config->get('is_setup');
|
||||
if ($icons === null) { // if the static variables have not been initialised
|
||||
$icons = [];
|
||||
// Icon definitions:
|
||||
// The same indexes will be used in the $icons array.
|
||||
// The first element contains the filename and the second
|
||||
// element is used for the "alt" and "title" attributes.
|
||||
$iconInit = [
|
||||
'edit' => [
|
||||
'b_edit',
|
||||
'',
|
||||
],
|
||||
'help' => [
|
||||
'b_help',
|
||||
__('Documentation'),
|
||||
],
|
||||
'reload' => [
|
||||
's_reload',
|
||||
'',
|
||||
],
|
||||
'tblops' => [
|
||||
'b_tblops',
|
||||
'',
|
||||
],
|
||||
];
|
||||
if ($isSetupScript) {
|
||||
// When called from the setup script, we don't have access to the
|
||||
// sprite-aware getImage() function because the PMA_theme class
|
||||
// has not been loaded, so we generate the img tags manually.
|
||||
foreach ($iconInit as $k => $v) {
|
||||
$title = '';
|
||||
if (! empty($v[1])) {
|
||||
$title = ' title="' . $v[1] . '"';
|
||||
}
|
||||
$icons[$k] = sprintf(
|
||||
'<img alt="%s" src="%s"%s>',
|
||||
$v[1],
|
||||
'../themes/pmahomme/img/' . $v[0] . '.png',
|
||||
$title
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// In this case we just use getImage() because it's available
|
||||
foreach ($iconInit as $k => $v) {
|
||||
$icons[$k] = Generator::getImage(
|
||||
$v[0],
|
||||
$v[1]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
$hasErrors = isset($opts['errors']) && ! empty($opts['errors']);
|
||||
$optionIsDisabled = ! $isSetupScript && isset($opts['userprefs_allow'])
|
||||
&& ! $opts['userprefs_allow'];
|
||||
$nameId = 'name="' . htmlspecialchars($path) . '" id="'
|
||||
. htmlspecialchars($path) . '"';
|
||||
$fieldClass = $type === 'checkbox' ? 'checkbox' : '';
|
||||
if (! $valueIsDefault) {
|
||||
$fieldClass .= ($fieldClass == '' ? '' : ' ')
|
||||
. ($hasErrors ? 'custom field-error' : 'custom');
|
||||
}
|
||||
$fieldClass = $fieldClass ? ' class="' . $fieldClass . '"' : '';
|
||||
$trClass = $this->group > 0
|
||||
? 'group-field group-field-' . $this->group
|
||||
: '';
|
||||
if (isset($opts['setvalue']) && $opts['setvalue'] === ':group') {
|
||||
unset($opts['setvalue']);
|
||||
$this->group++;
|
||||
$trClass = 'group-header-field group-header-' . $this->group;
|
||||
}
|
||||
if ($optionIsDisabled) {
|
||||
$trClass .= ($trClass ? ' ' : '') . 'disabled-field';
|
||||
}
|
||||
$trClass = $trClass ? ' class="' . $trClass . '"' : '';
|
||||
|
||||
$htmlOutput = '<tr' . $trClass . '>';
|
||||
$htmlOutput .= '<th>';
|
||||
$htmlOutput .= '<label for="' . htmlspecialchars($path) . '">' . htmlspecialchars_decode($name)
|
||||
. '</label>';
|
||||
|
||||
if (! empty($opts['doc'])) {
|
||||
$htmlOutput .= '<span class="doc">';
|
||||
$htmlOutput .= '<a href="' . $opts['doc']
|
||||
. '" target="documentation">' . $icons['help'] . '</a>';
|
||||
$htmlOutput .= "\n";
|
||||
$htmlOutput .= '</span>';
|
||||
}
|
||||
|
||||
if ($optionIsDisabled) {
|
||||
$htmlOutput .= '<span class="disabled-notice" title="';
|
||||
$htmlOutput .= __(
|
||||
'This setting is disabled, it will not be applied to your configuration.'
|
||||
);
|
||||
$htmlOutput .= '">' . __('Disabled') . '</span>';
|
||||
}
|
||||
|
||||
if (! empty($description)) {
|
||||
$htmlOutput .= '<small>' . $description . '</small>';
|
||||
}
|
||||
|
||||
$htmlOutput .= '</th>';
|
||||
$htmlOutput .= '<td>';
|
||||
|
||||
switch ($type) {
|
||||
case 'text':
|
||||
$htmlOutput .= '<input type="text" class="w-75" ' . $nameId . $fieldClass
|
||||
. ' value="' . htmlspecialchars($value) . '">';
|
||||
break;
|
||||
case 'password':
|
||||
$htmlOutput .= '<input type="password" class="w-75" ' . $nameId . $fieldClass
|
||||
. ' value="' . htmlspecialchars($value) . '">';
|
||||
break;
|
||||
case 'short_text':
|
||||
// As seen in the reporting server (#15042) we sometimes receive
|
||||
// an array here. No clue about its origin nor content, so let's avoid
|
||||
// a notice on htmlspecialchars().
|
||||
if (is_string($value)) {
|
||||
$htmlOutput .= '<input type="text" size="25" ' . $nameId
|
||||
. $fieldClass . ' value="' . htmlspecialchars($value)
|
||||
. '">';
|
||||
}
|
||||
break;
|
||||
case 'number_text':
|
||||
$htmlOutput .= '<input type="number" ' . $nameId . $fieldClass
|
||||
. ' value="' . htmlspecialchars((string) $value) . '">';
|
||||
break;
|
||||
case 'checkbox':
|
||||
$htmlOutput .= '<span' . $fieldClass . '><input type="checkbox" ' . $nameId
|
||||
. ($value ? ' checked="checked"' : '') . '></span>';
|
||||
break;
|
||||
case 'select':
|
||||
$htmlOutput .= '<select class="w-75" ' . $nameId . $fieldClass . '>';
|
||||
$escape = ! (isset($opts['values_escaped']) && $opts['values_escaped']);
|
||||
$valuesDisabled = isset($opts['values_disabled'])
|
||||
? array_flip($opts['values_disabled']) : [];
|
||||
foreach ($opts['values'] as $optValueKey => $optValue) {
|
||||
// set names for boolean values
|
||||
if (is_bool($optValue)) {
|
||||
$optValue = mb_strtolower(
|
||||
$optValue ? __('Yes') : __('No')
|
||||
);
|
||||
}
|
||||
// escape if necessary
|
||||
if ($escape) {
|
||||
$display = htmlspecialchars((string) $optValue);
|
||||
$displayValue = htmlspecialchars((string) $optValueKey);
|
||||
} else {
|
||||
$display = $optValue;
|
||||
$displayValue = $optValueKey;
|
||||
}
|
||||
// compare with selected value
|
||||
// boolean values are cast to integers when used as array keys
|
||||
$selected = is_bool($value)
|
||||
? (int) $value === $optValueKey
|
||||
: $optValueKey === $value;
|
||||
$htmlOutput .= '<option value="' . $displayValue . '"';
|
||||
if ($selected) {
|
||||
$htmlOutput .= ' selected="selected"';
|
||||
}
|
||||
if (isset($valuesDisabled[$optValueKey])) {
|
||||
$htmlOutput .= ' disabled="disabled"';
|
||||
}
|
||||
$htmlOutput .= '>' . $display . '</option>';
|
||||
}
|
||||
$htmlOutput .= '</select>';
|
||||
break;
|
||||
case 'list':
|
||||
$val = $value;
|
||||
if (isset($val['wrapper_params'])) {
|
||||
unset($val['wrapper_params']);
|
||||
}
|
||||
$htmlOutput .= '<textarea cols="35" rows="5" ' . $nameId . $fieldClass
|
||||
. '>' . htmlspecialchars(implode("\n", $val)) . '</textarea>';
|
||||
break;
|
||||
}
|
||||
if ($isSetupScript
|
||||
&& isset($opts['userprefs_comment'])
|
||||
&& $opts['userprefs_comment']
|
||||
) {
|
||||
$htmlOutput .= '<a class="userprefs-comment" title="'
|
||||
. htmlspecialchars($opts['userprefs_comment']) . '">'
|
||||
. $icons['tblops'] . '</a>';
|
||||
}
|
||||
if (isset($opts['setvalue']) && $opts['setvalue']) {
|
||||
$htmlOutput .= '<a class="set-value hide" href="#'
|
||||
. htmlspecialchars($path . '=' . $opts['setvalue']) . '" title="'
|
||||
. sprintf(__('Set value: %s'), htmlspecialchars($opts['setvalue']))
|
||||
. '">' . $icons['edit'] . '</a>';
|
||||
}
|
||||
if (isset($opts['show_restore_default']) && $opts['show_restore_default']) {
|
||||
$htmlOutput .= '<a class="restore-default hide" href="#' . $path . '" title="'
|
||||
. __('Restore default value') . '">' . $icons['reload'] . '</a>';
|
||||
}
|
||||
// this must match with displayErrors() in scripts/config.js
|
||||
if ($hasErrors) {
|
||||
$htmlOutput .= "\n <dl class=\"inline_errors\">";
|
||||
foreach ($opts['errors'] as $error) {
|
||||
$htmlOutput .= '<dd>' . htmlspecialchars($error) . '</dd>';
|
||||
}
|
||||
$htmlOutput .= '</dl>';
|
||||
}
|
||||
$htmlOutput .= '</td>';
|
||||
if ($isSetupScript && isset($opts['userprefs_allow'])) {
|
||||
$htmlOutput .= '<td class="userprefs-allow" title="' .
|
||||
__('Allow users to customize this value') . '">';
|
||||
$htmlOutput .= '<input type="checkbox" name="' . $path
|
||||
. '-userprefs-allow" ';
|
||||
if ($opts['userprefs_allow']) {
|
||||
$htmlOutput .= 'checked="checked"';
|
||||
}
|
||||
$htmlOutput .= '>';
|
||||
$htmlOutput .= '</td>';
|
||||
} elseif ($isSetupScript) {
|
||||
$htmlOutput .= '<td> </td>';
|
||||
}
|
||||
$htmlOutput .= '</tr>';
|
||||
|
||||
return $htmlOutput;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display group header
|
||||
*
|
||||
* @param string $headerText Text of header
|
||||
*/
|
||||
public function displayGroupHeader(string $headerText): string
|
||||
{
|
||||
$this->group++;
|
||||
if ($headerText === '') {
|
||||
return '';
|
||||
}
|
||||
$colspan = $this->config->get('is_setup') ? 3 : 2;
|
||||
|
||||
return $this->template->render('config/form_display/group_header', [
|
||||
'group' => $this->group,
|
||||
'colspan' => $colspan,
|
||||
'header_text' => $headerText,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display group footer
|
||||
*/
|
||||
public function displayGroupFooter(): void
|
||||
{
|
||||
$this->group--;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays bottom part of a fieldset
|
||||
*
|
||||
* @param bool $showButtons Whether show submit and reset button
|
||||
*/
|
||||
public function displayFieldsetBottom(bool $showButtons = true): string
|
||||
{
|
||||
return $this->template->render('config/form_display/fieldset_bottom', [
|
||||
'show_buttons' => $showButtons,
|
||||
'is_setup' => $this->config->get('is_setup'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes form tabs
|
||||
*/
|
||||
public function displayTabsBottom(): string
|
||||
{
|
||||
return $this->template->render('config/form_display/tabs_bottom');
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays bottom part of the form
|
||||
*/
|
||||
public function displayFormBottom(): string
|
||||
{
|
||||
return $this->template->render('config/form_display/form_bottom');
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends JS validation code to $js_array
|
||||
*
|
||||
* @param string $fieldId ID of field to validate
|
||||
* @param string|array $validators validators callback
|
||||
* @param array $jsArray will be updated with javascript code
|
||||
*/
|
||||
public function addJsValidate($fieldId, $validators, array &$jsArray): void
|
||||
{
|
||||
foreach ((array) $validators as $validator) {
|
||||
$validator = (array) $validator;
|
||||
$vName = array_shift($validator);
|
||||
$vArgs = [];
|
||||
foreach ($validator as $arg) {
|
||||
$vArgs[] = Sanitize::escapeJsString($arg);
|
||||
}
|
||||
$vArgs = $vArgs ? ", ['" . implode("', '", $vArgs) . "']" : '';
|
||||
$jsArray[] = "registerFieldValidator('" . $fieldId . "', '" . $vName . "', true" . $vArgs . ')';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays JavaScript code
|
||||
*
|
||||
* @param array $jsArray lines of javascript code
|
||||
*/
|
||||
public function displayJavascript(array $jsArray): string
|
||||
{
|
||||
if (empty($jsArray)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $this->template->render('javascript/display', ['js_array' => $jsArray]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays error list
|
||||
*
|
||||
* @param string $name Name of item with errors
|
||||
* @param array $errorList List of errors to show
|
||||
*
|
||||
* @return string HTML for errors
|
||||
*/
|
||||
public function displayErrors($name, array $errorList): string
|
||||
{
|
||||
return $this->template->render('config/form_display/errors', [
|
||||
'name' => $name,
|
||||
'error_list' => $errorList,
|
||||
]);
|
||||
}
|
||||
}
|
85
admin/phpMyAdmin/libraries/classes/Config/Forms/BaseForm.php
Normal file
85
admin/phpMyAdmin/libraries/classes/Config/Forms/BaseForm.php
Normal file
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
/**
|
||||
* Base class for preferences.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config\Forms;
|
||||
|
||||
use PhpMyAdmin\Config\ConfigFile;
|
||||
use PhpMyAdmin\Config\FormDisplay;
|
||||
use function is_int;
|
||||
|
||||
/**
|
||||
* Base form for user preferences
|
||||
*/
|
||||
abstract class BaseForm extends FormDisplay
|
||||
{
|
||||
/**
|
||||
* @param ConfigFile $cf Config file instance
|
||||
* @param int|null $serverId 0 if new server, validation; >= 1 if editing a server
|
||||
*/
|
||||
public function __construct(ConfigFile $cf, $serverId = null)
|
||||
{
|
||||
parent::__construct($cf);
|
||||
foreach (static::getForms() as $formName => $form) {
|
||||
$this->registerForm($formName, $form, $serverId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List of available forms, each form is described as an array of fields to display.
|
||||
* Fields MUST have their counterparts in the $cfg array.
|
||||
*
|
||||
* To define form field, use the notation below:
|
||||
* $forms['Form group']['Form name'] = array('Option/path');
|
||||
*
|
||||
* You can assign default values set by special button ("set value: ..."), eg.:
|
||||
* 'Servers/1/pmadb' => 'phpmyadmin'
|
||||
*
|
||||
* To group options, use:
|
||||
* ':group:' . __('group name') // just define a group
|
||||
* or
|
||||
* 'option' => ':group' // group starting from this option
|
||||
* End group blocks with:
|
||||
* ':group:end'
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @todo This should be abstract, but that does not work in PHP 5
|
||||
*/
|
||||
public static function getForms()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns list of fields used in the form.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function getFields()
|
||||
{
|
||||
$names = [];
|
||||
foreach (static::getForms() as $form) {
|
||||
foreach ($form as $k => $v) {
|
||||
$names[] = is_int($k) ? $v : $k;
|
||||
}
|
||||
}
|
||||
|
||||
return $names;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns name of the form
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @todo This should be abstract, but that does not work in PHP 5
|
||||
*/
|
||||
public static function getName()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
}
|
149
admin/phpMyAdmin/libraries/classes/Config/Forms/BaseFormList.php
Normal file
149
admin/phpMyAdmin/libraries/classes/Config/Forms/BaseFormList.php
Normal file
|
@ -0,0 +1,149 @@
|
|||
<?php
|
||||
/**
|
||||
* User preferences form
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config\Forms;
|
||||
|
||||
use PhpMyAdmin\Config\ConfigFile;
|
||||
use function array_merge;
|
||||
use function in_array;
|
||||
|
||||
class BaseFormList
|
||||
{
|
||||
/**
|
||||
* List of all forms
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $all = [];
|
||||
|
||||
/** @var string */
|
||||
protected static $ns = 'PhpMyAdmin\\Config\\Forms\\';
|
||||
|
||||
/** @var array */
|
||||
private $forms;
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getAll()
|
||||
{
|
||||
return static::$all;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name Name
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isValid($name)
|
||||
{
|
||||
return in_array($name, static::$all);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name Name
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public static function get($name)
|
||||
{
|
||||
if (static::isValid($name)) {
|
||||
return static::$ns . $name . 'Form';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ConfigFile $cf Config file instance
|
||||
*/
|
||||
public function __construct(ConfigFile $cf)
|
||||
{
|
||||
$this->forms = [];
|
||||
foreach (static::$all as $form) {
|
||||
$class = static::get($form);
|
||||
$this->forms[] = new $class($cf);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes forms, returns true on successful save
|
||||
*
|
||||
* @param bool $allowPartialSave allows for partial form saving
|
||||
* on failed validation
|
||||
* @param bool $checkFormSubmit whether check for $_POST['submit_save']
|
||||
*
|
||||
* @return bool whether processing was successful
|
||||
*/
|
||||
public function process($allowPartialSave = true, $checkFormSubmit = true)
|
||||
{
|
||||
$ret = true;
|
||||
foreach ($this->forms as $form) {
|
||||
$ret = $ret && $form->process($allowPartialSave, $checkFormSubmit);
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays errors
|
||||
*
|
||||
* @return string HTML for errors
|
||||
*/
|
||||
public function displayErrors()
|
||||
{
|
||||
$ret = '';
|
||||
foreach ($this->forms as $form) {
|
||||
$ret .= $form->displayErrors();
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverts erroneous fields to their default values
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function fixErrors()
|
||||
{
|
||||
foreach ($this->forms as $form) {
|
||||
$form->fixErrors();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells whether form validation failed
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasErrors()
|
||||
{
|
||||
$ret = false;
|
||||
foreach ($this->forms as $form) {
|
||||
$ret = $ret || $form->hasErrors();
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns list of fields used in the form.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function getFields()
|
||||
{
|
||||
$names = [];
|
||||
foreach (static::$all as $form) {
|
||||
$class = static::get($form);
|
||||
$names = array_merge($names, $class::getFields());
|
||||
}
|
||||
|
||||
return $names;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
/**
|
||||
* User preferences form
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config\Forms\Page;
|
||||
|
||||
use PhpMyAdmin\Config\Forms\BaseForm;
|
||||
use PhpMyAdmin\Config\Forms\User\MainForm;
|
||||
|
||||
class BrowseForm extends BaseForm
|
||||
{
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getForms()
|
||||
{
|
||||
return [
|
||||
'Browse' => MainForm::getForms()['Browse'],
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
/**
|
||||
* User preferences form
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config\Forms\Page;
|
||||
|
||||
use PhpMyAdmin\Config\Forms\BaseForm;
|
||||
use PhpMyAdmin\Config\Forms\User\MainForm;
|
||||
|
||||
class DbStructureForm extends BaseForm
|
||||
{
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getForms()
|
||||
{
|
||||
return [
|
||||
'DbStructure' => MainForm::getForms()['DbStructure'],
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
/**
|
||||
* User preferences form
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config\Forms\Page;
|
||||
|
||||
use PhpMyAdmin\Config\Forms\BaseForm;
|
||||
use PhpMyAdmin\Config\Forms\User\FeaturesForm;
|
||||
use PhpMyAdmin\Config\Forms\User\MainForm;
|
||||
|
||||
class EditForm extends BaseForm
|
||||
{
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getForms()
|
||||
{
|
||||
return [
|
||||
'Edit' => MainForm::getForms()['Edit'],
|
||||
'Text_fields' => FeaturesForm::getForms()['Text_fields'],
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
/**
|
||||
* User preferences form
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config\Forms\Page;
|
||||
|
||||
class ExportForm extends \PhpMyAdmin\Config\Forms\User\ExportForm
|
||||
{
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
/**
|
||||
* User preferences form
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config\Forms\Page;
|
||||
|
||||
class ImportForm extends \PhpMyAdmin\Config\Forms\User\ImportForm
|
||||
{
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
/**
|
||||
* User preferences form
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config\Forms\Page;
|
||||
|
||||
class NaviForm extends \PhpMyAdmin\Config\Forms\User\NaviForm
|
||||
{
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
/**
|
||||
* Page preferences form
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config\Forms\Page;
|
||||
|
||||
use PhpMyAdmin\Config\Forms\BaseFormList;
|
||||
|
||||
class PageFormList extends BaseFormList
|
||||
{
|
||||
/** @var array */
|
||||
protected static $all = [
|
||||
'Browse',
|
||||
'DbStructure',
|
||||
'Edit',
|
||||
'Export',
|
||||
'Import',
|
||||
'Navi',
|
||||
'Sql',
|
||||
'TableStructure',
|
||||
];
|
||||
/** @var string */
|
||||
protected static $ns = '\\PhpMyAdmin\\Config\\Forms\\Page\\';
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
/**
|
||||
* User preferences form
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config\Forms\Page;
|
||||
|
||||
class SqlForm extends \PhpMyAdmin\Config\Forms\User\SqlForm
|
||||
{
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
/**
|
||||
* User preferences form
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config\Forms\Page;
|
||||
|
||||
use PhpMyAdmin\Config\Forms\BaseForm;
|
||||
use PhpMyAdmin\Config\Forms\User\MainForm;
|
||||
|
||||
class TableStructureForm extends BaseForm
|
||||
{
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getForms()
|
||||
{
|
||||
return [
|
||||
'TableStructure' => MainForm::getForms()['TableStructure'],
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
/**
|
||||
* User preferences form
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config\Forms\Setup;
|
||||
|
||||
use PhpMyAdmin\Config\Forms\BaseForm;
|
||||
|
||||
class ConfigForm extends BaseForm
|
||||
{
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getForms()
|
||||
{
|
||||
return [
|
||||
'Config' => [
|
||||
'DefaultLang',
|
||||
'ServerDefault',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
/**
|
||||
* User preferences form
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config\Forms\Setup;
|
||||
|
||||
class ExportForm extends \PhpMyAdmin\Config\Forms\User\ExportForm
|
||||
{
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
/**
|
||||
* User preferences form
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config\Forms\Setup;
|
||||
|
||||
use function array_diff;
|
||||
|
||||
class FeaturesForm extends \PhpMyAdmin\Config\Forms\User\FeaturesForm
|
||||
{
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getForms()
|
||||
{
|
||||
// phpcs:disable Squiz.Arrays.ArrayDeclaration.KeySpecified,Squiz.Arrays.ArrayDeclaration.NoKeySpecified
|
||||
$result = parent::getForms();
|
||||
/* Remove only_db/hide_db, we have proper Server form in setup */
|
||||
$result['Databases'] = array_diff(
|
||||
$result['Databases'],
|
||||
[
|
||||
'Servers/1/only_db',
|
||||
'Servers/1/hide_db',
|
||||
]
|
||||
);
|
||||
/* Following are not available to user */
|
||||
$result['Import_export'] = [
|
||||
'UploadDir',
|
||||
'SaveDir',
|
||||
'RecodingEngine' => ':group',
|
||||
'IconvExtraParams',
|
||||
':group:end',
|
||||
'ZipDump',
|
||||
'GZipDump',
|
||||
'BZipDump',
|
||||
'CompressOnFly',
|
||||
];
|
||||
$result['Security'] = [
|
||||
'blowfish_secret',
|
||||
'CheckConfigurationPermissions',
|
||||
'TrustedProxies',
|
||||
'AllowUserDropDatabase',
|
||||
'AllowArbitraryServer',
|
||||
'ArbitraryServerRegexp',
|
||||
'LoginCookieRecall',
|
||||
'LoginCookieStore',
|
||||
'LoginCookieDeleteAll',
|
||||
'CaptchaLoginPublicKey',
|
||||
'CaptchaLoginPrivateKey',
|
||||
'CaptchaSiteVerifyURL',
|
||||
];
|
||||
$result['Developer'] = [
|
||||
'UserprefsDeveloperTab',
|
||||
'DBG/sql',
|
||||
];
|
||||
$result['Other_core_settings'] = [
|
||||
'OBGzip',
|
||||
'PersistentConnections',
|
||||
'ExecTimeLimit',
|
||||
'MemoryLimit',
|
||||
'UseDbSearch',
|
||||
'ProxyUrl',
|
||||
'ProxyUser',
|
||||
'ProxyPass',
|
||||
'AllowThirdPartyFraming',
|
||||
'ZeroConf',
|
||||
];
|
||||
|
||||
return $result;
|
||||
|
||||
// phpcs:enable
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
/**
|
||||
* User preferences form
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config\Forms\Setup;
|
||||
|
||||
class ImportForm extends \PhpMyAdmin\Config\Forms\User\ImportForm
|
||||
{
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
/**
|
||||
* User preferences form
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config\Forms\Setup;
|
||||
|
||||
class MainForm extends \PhpMyAdmin\Config\Forms\User\MainForm
|
||||
{
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getForms()
|
||||
{
|
||||
$result = parent::getForms();
|
||||
/* Following are not available to user */
|
||||
$result['Startup'][] = 'ShowPhpInfo';
|
||||
$result['Startup'][] = 'ShowChgPassword';
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
/**
|
||||
* User preferences form
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config\Forms\Setup;
|
||||
|
||||
class NaviForm extends \PhpMyAdmin\Config\Forms\User\NaviForm
|
||||
{
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
<?php
|
||||
/**
|
||||
* User preferences form
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config\Forms\Setup;
|
||||
|
||||
use PhpMyAdmin\Config\Forms\BaseForm;
|
||||
|
||||
class ServersForm extends BaseForm
|
||||
{
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getForms()
|
||||
{
|
||||
// phpcs:disable Squiz.Arrays.ArrayDeclaration.KeySpecified,Squiz.Arrays.ArrayDeclaration.NoKeySpecified
|
||||
return [
|
||||
'Server' => [
|
||||
'Servers' => [
|
||||
1 => [
|
||||
'verbose',
|
||||
'host',
|
||||
'port',
|
||||
'socket',
|
||||
'ssl',
|
||||
'compress',
|
||||
],
|
||||
],
|
||||
],
|
||||
'Server_auth' => [
|
||||
'Servers' => [
|
||||
1 => [
|
||||
'auth_type',
|
||||
':group:' . __('Config authentication'),
|
||||
'user',
|
||||
'password',
|
||||
':group:end',
|
||||
':group:' . __('HTTP authentication'),
|
||||
'auth_http_realm',
|
||||
':group:end',
|
||||
':group:' . __('Signon authentication'),
|
||||
'SignonSession',
|
||||
'SignonURL',
|
||||
'LogoutURL',
|
||||
],
|
||||
],
|
||||
],
|
||||
'Server_config' => [
|
||||
'Servers' => [
|
||||
1 => [
|
||||
'only_db',
|
||||
'hide_db',
|
||||
'AllowRoot',
|
||||
'AllowNoPassword',
|
||||
'DisableIS',
|
||||
'AllowDeny/order',
|
||||
'AllowDeny/rules',
|
||||
'SessionTimeZone',
|
||||
],
|
||||
],
|
||||
],
|
||||
'Server_pmadb' => [
|
||||
'Servers' => [
|
||||
1 => [
|
||||
'pmadb' => 'phpmyadmin',
|
||||
'controlhost',
|
||||
'controlport',
|
||||
'controluser',
|
||||
'controlpass',
|
||||
'bookmarktable' => 'pma__bookmark',
|
||||
'relation' => 'pma__relation',
|
||||
'userconfig' => 'pma__userconfig',
|
||||
'users' => 'pma__users',
|
||||
'usergroups' => 'pma__usergroups',
|
||||
'navigationhiding' => 'pma__navigationhiding',
|
||||
'table_info' => 'pma__table_info',
|
||||
'column_info' => 'pma__column_info',
|
||||
'history' => 'pma__history',
|
||||
'recent' => 'pma__recent',
|
||||
'favorite' => 'pma__favorite',
|
||||
'table_uiprefs' => 'pma__table_uiprefs',
|
||||
'tracking' => 'pma__tracking',
|
||||
'table_coords' => 'pma__table_coords',
|
||||
'pdf_pages' => 'pma__pdf_pages',
|
||||
'savedsearches' => 'pma__savedsearches',
|
||||
'central_columns' => 'pma__central_columns',
|
||||
'designer_settings' => 'pma__designer_settings',
|
||||
'export_templates' => 'pma__export_templates',
|
||||
'MaxTableUiprefs' => 100,
|
||||
],
|
||||
],
|
||||
],
|
||||
'Server_tracking' => [
|
||||
'Servers' => [
|
||||
1 => [
|
||||
'tracking_version_auto_create',
|
||||
'tracking_default_statements',
|
||||
'tracking_add_drop_view',
|
||||
'tracking_add_drop_table',
|
||||
'tracking_add_drop_database',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
// phpcs:enable
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
/**
|
||||
* Setup preferences form
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config\Forms\Setup;
|
||||
|
||||
use PhpMyAdmin\Config\Forms\BaseFormList;
|
||||
|
||||
class SetupFormList extends BaseFormList
|
||||
{
|
||||
/** @var array */
|
||||
protected static $all = [
|
||||
'Config',
|
||||
'Export',
|
||||
'Features',
|
||||
'Import',
|
||||
'Main',
|
||||
'Navi',
|
||||
'Servers',
|
||||
'Sql',
|
||||
];
|
||||
/** @var string */
|
||||
protected static $ns = '\\PhpMyAdmin\\Config\\Forms\\Setup\\';
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
/**
|
||||
* User preferences form
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config\Forms\Setup;
|
||||
|
||||
class SqlForm extends \PhpMyAdmin\Config\Forms\User\SqlForm
|
||||
{
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getForms()
|
||||
{
|
||||
$result = parent::getForms();
|
||||
/* Following are not available to user */
|
||||
$result['Sql_queries'][] = 'QueryHistoryDB';
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
<?php
|
||||
/**
|
||||
* User preferences form
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config\Forms\User;
|
||||
|
||||
use PhpMyAdmin\Config\Forms\BaseForm;
|
||||
|
||||
class ExportForm extends BaseForm
|
||||
{
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getForms()
|
||||
{
|
||||
// phpcs:disable Squiz.Arrays.ArrayDeclaration.KeySpecified,Squiz.Arrays.ArrayDeclaration.NoKeySpecified
|
||||
return [
|
||||
'Export_defaults' => [
|
||||
'Export/method',
|
||||
':group:' . __('Quick'),
|
||||
'Export/quick_export_onserver',
|
||||
'Export/quick_export_onserver_overwrite',
|
||||
':group:end',
|
||||
':group:' . __('Custom'),
|
||||
'Export/format',
|
||||
'Export/compression',
|
||||
'Export/charset',
|
||||
'Export/lock_tables',
|
||||
'Export/as_separate_files',
|
||||
'Export/asfile' => ':group',
|
||||
'Export/onserver',
|
||||
'Export/onserver_overwrite',
|
||||
':group:end',
|
||||
'Export/file_template_table',
|
||||
'Export/file_template_database',
|
||||
'Export/file_template_server',
|
||||
],
|
||||
'Sql' => [
|
||||
'Export/sql_include_comments' => ':group',
|
||||
'Export/sql_dates',
|
||||
'Export/sql_relation',
|
||||
'Export/sql_mime',
|
||||
':group:end',
|
||||
'Export/sql_use_transaction',
|
||||
'Export/sql_disable_fk',
|
||||
'Export/sql_views_as_tables',
|
||||
'Export/sql_metadata',
|
||||
'Export/sql_compatibility',
|
||||
'Export/sql_structure_or_data',
|
||||
':group:' . __('Structure'),
|
||||
'Export/sql_drop_database',
|
||||
'Export/sql_create_database',
|
||||
'Export/sql_drop_table',
|
||||
'Export/sql_create_table' => ':group',
|
||||
'Export/sql_if_not_exists',
|
||||
'Export/sql_auto_increment',
|
||||
':group:end',
|
||||
'Export/sql_create_view' => ':group',
|
||||
'Export/sql_view_current_user',
|
||||
'Export/sql_or_replace_view',
|
||||
':group:end',
|
||||
'Export/sql_procedure_function',
|
||||
'Export/sql_create_trigger',
|
||||
'Export/sql_backquotes',
|
||||
':group:end',
|
||||
':group:' . __('Data'),
|
||||
'Export/sql_delayed',
|
||||
'Export/sql_ignore',
|
||||
'Export/sql_type',
|
||||
'Export/sql_insert_syntax',
|
||||
'Export/sql_max_query_size',
|
||||
'Export/sql_hex_for_binary',
|
||||
'Export/sql_utc_time',
|
||||
],
|
||||
'CodeGen' => ['Export/codegen_format'],
|
||||
'Csv' => [
|
||||
':group:' . __('CSV'),
|
||||
'Export/csv_separator',
|
||||
'Export/csv_enclosed',
|
||||
'Export/csv_escaped',
|
||||
'Export/csv_terminated',
|
||||
'Export/csv_null',
|
||||
'Export/csv_removeCRLF',
|
||||
'Export/csv_columns',
|
||||
':group:end',
|
||||
':group:' . __('CSV for MS Excel'),
|
||||
'Export/excel_null',
|
||||
'Export/excel_removeCRLF',
|
||||
'Export/excel_columns',
|
||||
'Export/excel_edition',
|
||||
],
|
||||
'Latex' => [
|
||||
'Export/latex_caption',
|
||||
'Export/latex_structure_or_data',
|
||||
':group:' . __('Structure'),
|
||||
'Export/latex_structure_caption',
|
||||
'Export/latex_structure_continued_caption',
|
||||
'Export/latex_structure_label',
|
||||
'Export/latex_relation',
|
||||
'Export/latex_comments',
|
||||
'Export/latex_mime',
|
||||
':group:end',
|
||||
':group:' . __('Data'),
|
||||
'Export/latex_columns',
|
||||
'Export/latex_data_caption',
|
||||
'Export/latex_data_continued_caption',
|
||||
'Export/latex_data_label',
|
||||
'Export/latex_null',
|
||||
],
|
||||
'Microsoft_Office' => [
|
||||
':group:' . __('Microsoft Word 2000'),
|
||||
'Export/htmlword_structure_or_data',
|
||||
'Export/htmlword_null',
|
||||
'Export/htmlword_columns',
|
||||
],
|
||||
'Open_Document' => [
|
||||
':group:' . __('OpenDocument Spreadsheet'),
|
||||
'Export/ods_columns',
|
||||
'Export/ods_null',
|
||||
':group:end',
|
||||
':group:' . __('OpenDocument Text'),
|
||||
'Export/odt_structure_or_data',
|
||||
':group:' . __('Structure'),
|
||||
'Export/odt_relation',
|
||||
'Export/odt_comments',
|
||||
'Export/odt_mime',
|
||||
':group:end',
|
||||
':group:' . __('Data'),
|
||||
'Export/odt_columns',
|
||||
'Export/odt_null',
|
||||
],
|
||||
'Texy' => [
|
||||
'Export/texytext_structure_or_data',
|
||||
':group:' . __('Data'),
|
||||
'Export/texytext_null',
|
||||
'Export/texytext_columns',
|
||||
],
|
||||
];
|
||||
|
||||
// phpcs:enable
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function getName()
|
||||
{
|
||||
return __('Export');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
/**
|
||||
* User preferences form
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config\Forms\User;
|
||||
|
||||
use PhpMyAdmin\Config\Forms\BaseForm;
|
||||
|
||||
class FeaturesForm extends BaseForm
|
||||
{
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getForms()
|
||||
{
|
||||
$result = [
|
||||
'General' => [
|
||||
'VersionCheck',
|
||||
'NaturalOrder',
|
||||
'InitialSlidersState',
|
||||
'LoginCookieValidity',
|
||||
'SkipLockedTables',
|
||||
'DisableMultiTableMaintenance',
|
||||
'ShowHint',
|
||||
'SendErrorReports',
|
||||
'ConsoleEnterExecutes',
|
||||
'DisableShortcutKeys',
|
||||
'FirstDayOfCalendar',
|
||||
],
|
||||
'Databases' => [
|
||||
'Servers/1/only_db', // saves to Server/only_db
|
||||
'Servers/1/hide_db', // saves to Server/hide_db
|
||||
'MaxDbList',
|
||||
'MaxTableList',
|
||||
'DefaultConnectionCollation',
|
||||
],
|
||||
'Text_fields' => [
|
||||
'CharEditing',
|
||||
'MinSizeForInputField',
|
||||
'MaxSizeForInputField',
|
||||
'CharTextareaCols',
|
||||
'CharTextareaRows',
|
||||
'TextareaCols',
|
||||
'TextareaRows',
|
||||
'LongtextDoubleTextarea',
|
||||
],
|
||||
'Page_titles' => [
|
||||
'TitleDefault',
|
||||
'TitleTable',
|
||||
'TitleDatabase',
|
||||
'TitleServer',
|
||||
],
|
||||
'Warnings' => [
|
||||
'PmaNoRelation_DisableWarning',
|
||||
'SuhosinDisableWarning',
|
||||
'LoginCookieValidityDisableWarning',
|
||||
'ReservedWordDisableWarning',
|
||||
],
|
||||
'Console' => [
|
||||
'Console/Mode',
|
||||
'Console/StartHistory',
|
||||
'Console/AlwaysExpand',
|
||||
'Console/CurrentQuery',
|
||||
'Console/EnterExecutes',
|
||||
'Console/DarkTheme',
|
||||
'Console/Height',
|
||||
'Console/GroupQueries',
|
||||
'Console/OrderBy',
|
||||
'Console/Order',
|
||||
],
|
||||
];
|
||||
// skip Developer form if no setting is available
|
||||
if ($GLOBALS['cfg']['UserprefsDeveloperTab']) {
|
||||
$result['Developer'] = ['DBG/sql'];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function getName()
|
||||
{
|
||||
return __('Features');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
/**
|
||||
* User preferences form
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config\Forms\User;
|
||||
|
||||
use PhpMyAdmin\Config\Forms\BaseForm;
|
||||
|
||||
class ImportForm extends BaseForm
|
||||
{
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getForms()
|
||||
{
|
||||
return [
|
||||
'Import_defaults' => [
|
||||
'Import/format',
|
||||
'Import/charset',
|
||||
'Import/allow_interrupt',
|
||||
'Import/skip_queries',
|
||||
'enable_drag_drop_import',
|
||||
],
|
||||
'Sql' => [
|
||||
'Import/sql_compatibility',
|
||||
'Import/sql_no_auto_value_on_zero',
|
||||
'Import/sql_read_as_multibytes',
|
||||
],
|
||||
'Csv' => [
|
||||
':group:' . __('CSV'),
|
||||
'Import/csv_replace',
|
||||
'Import/csv_ignore',
|
||||
'Import/csv_terminated',
|
||||
'Import/csv_enclosed',
|
||||
'Import/csv_escaped',
|
||||
'Import/csv_col_names',
|
||||
':group:end',
|
||||
':group:' . __('CSV using LOAD DATA'),
|
||||
'Import/ldi_replace',
|
||||
'Import/ldi_ignore',
|
||||
'Import/ldi_terminated',
|
||||
'Import/ldi_enclosed',
|
||||
'Import/ldi_escaped',
|
||||
'Import/ldi_local_option',
|
||||
],
|
||||
'Open_Document' => [
|
||||
':group:' . __('OpenDocument Spreadsheet'),
|
||||
'Import/ods_col_names',
|
||||
'Import/ods_empty_rows',
|
||||
'Import/ods_recognize_percentages',
|
||||
'Import/ods_recognize_currency',
|
||||
],
|
||||
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function getName()
|
||||
{
|
||||
return __('Import');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
/**
|
||||
* User preferences form
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config\Forms\User;
|
||||
|
||||
use PhpMyAdmin\Config\Forms\BaseForm;
|
||||
|
||||
class MainForm extends BaseForm
|
||||
{
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getForms()
|
||||
{
|
||||
return [
|
||||
'Startup' => [
|
||||
'ShowCreateDb',
|
||||
'ShowStats',
|
||||
'ShowServerInfo',
|
||||
],
|
||||
'DbStructure' => [
|
||||
'ShowDbStructureCharset',
|
||||
'ShowDbStructureComment',
|
||||
'ShowDbStructureCreation',
|
||||
'ShowDbStructureLastUpdate',
|
||||
'ShowDbStructureLastCheck',
|
||||
],
|
||||
'TableStructure' => [
|
||||
'HideStructureActions',
|
||||
'ShowColumnComments',
|
||||
':group:' . __('Default transformations'),
|
||||
'DefaultTransformations/Hex',
|
||||
'DefaultTransformations/Substring',
|
||||
'DefaultTransformations/Bool2Text',
|
||||
'DefaultTransformations/External',
|
||||
'DefaultTransformations/PreApPend',
|
||||
'DefaultTransformations/DateFormat',
|
||||
'DefaultTransformations/Inline',
|
||||
'DefaultTransformations/TextImageLink',
|
||||
'DefaultTransformations/TextLink',
|
||||
':group:end',
|
||||
],
|
||||
'Browse' => [
|
||||
'TableNavigationLinksMode',
|
||||
'ActionLinksMode',
|
||||
'ShowAll',
|
||||
'MaxRows',
|
||||
'Order',
|
||||
'BrowsePointerEnable',
|
||||
'BrowseMarkerEnable',
|
||||
'GridEditing',
|
||||
'SaveCellsAtOnce',
|
||||
'RepeatCells',
|
||||
'LimitChars',
|
||||
'RowActionLinks',
|
||||
'RowActionLinksWithoutUnique',
|
||||
'TablePrimaryKeyOrder',
|
||||
'RememberSorting',
|
||||
'RelationalDisplay',
|
||||
],
|
||||
'Edit' => [
|
||||
'ProtectBinary',
|
||||
'ShowFunctionFields',
|
||||
'ShowFieldTypesInDataEditView',
|
||||
'InsertRows',
|
||||
'ForeignKeyDropdownOrder',
|
||||
'ForeignKeyMaxLimit',
|
||||
],
|
||||
'Tabs' => [
|
||||
'TabsMode',
|
||||
'DefaultTabServer',
|
||||
'DefaultTabDatabase',
|
||||
'DefaultTabTable',
|
||||
],
|
||||
'DisplayRelationalSchema' => ['PDFDefaultPageSize'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function getName()
|
||||
{
|
||||
return __('Main panel');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
/**
|
||||
* User preferences form
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config\Forms\User;
|
||||
|
||||
use PhpMyAdmin\Config\Forms\BaseForm;
|
||||
|
||||
class NaviForm extends BaseForm
|
||||
{
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getForms()
|
||||
{
|
||||
return [
|
||||
'Navi_panel' => [
|
||||
'ShowDatabasesNavigationAsTree',
|
||||
'NavigationLinkWithMainPanel',
|
||||
'NavigationDisplayLogo',
|
||||
'NavigationLogoLink',
|
||||
'NavigationLogoLinkWindow',
|
||||
'NavigationTreePointerEnable',
|
||||
'FirstLevelNavigationItems',
|
||||
'NavigationTreeDisplayItemFilterMinimum',
|
||||
'NumRecentTables',
|
||||
'NumFavoriteTables',
|
||||
'NavigationWidth',
|
||||
],
|
||||
'Navi_tree' => [
|
||||
'MaxNavigationItems',
|
||||
'NavigationTreeEnableGrouping',
|
||||
'NavigationTreeEnableExpansion',
|
||||
'NavigationTreeShowTables',
|
||||
'NavigationTreeShowViews',
|
||||
'NavigationTreeShowFunctions',
|
||||
'NavigationTreeShowProcedures',
|
||||
'NavigationTreeShowEvents',
|
||||
'NavigationTreeAutoexpandSingleDb',
|
||||
],
|
||||
'Navi_servers' => [
|
||||
'NavigationDisplayServers',
|
||||
'DisplayServersList',
|
||||
],
|
||||
'Navi_databases' => [
|
||||
'NavigationTreeDisplayDbFilterMinimum',
|
||||
'NavigationTreeDbSeparator',
|
||||
],
|
||||
'Navi_tables' => [
|
||||
'NavigationTreeDefaultTabTable',
|
||||
'NavigationTreeDefaultTabTable2',
|
||||
'NavigationTreeTableSeparator',
|
||||
'NavigationTreeTableLevel',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function getName()
|
||||
{
|
||||
return __('Navigation panel');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
/**
|
||||
* User preferences form
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config\Forms\User;
|
||||
|
||||
use PhpMyAdmin\Config\Forms\BaseForm;
|
||||
|
||||
class SqlForm extends BaseForm
|
||||
{
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getForms()
|
||||
{
|
||||
return [
|
||||
'Sql_queries' => [
|
||||
'ShowSQL',
|
||||
'Confirm',
|
||||
'QueryHistoryMax',
|
||||
'IgnoreMultiSubmitErrors',
|
||||
'MaxCharactersInDisplayedSQL',
|
||||
'RetainQueryBox',
|
||||
'CodemirrorEnable',
|
||||
'LintEnable',
|
||||
'EnableAutocompleteForTablesAndColumns',
|
||||
'DefaultForeignKeyChecks',
|
||||
],
|
||||
'Sql_box' => [
|
||||
'SQLQuery/Edit',
|
||||
'SQLQuery/Explain',
|
||||
'SQLQuery/ShowAsPHP',
|
||||
'SQLQuery/Refresh',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function getName()
|
||||
{
|
||||
return __('SQL queries');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
/**
|
||||
* User preferences form
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config\Forms\User;
|
||||
|
||||
use PhpMyAdmin\Config\Forms\BaseFormList;
|
||||
|
||||
class UserFormList extends BaseFormList
|
||||
{
|
||||
/** @var array */
|
||||
protected static $all = [
|
||||
'Features',
|
||||
'Sql',
|
||||
'Navi',
|
||||
'Main',
|
||||
'Export',
|
||||
'Import',
|
||||
];
|
||||
/** @var string */
|
||||
protected static $ns = '\\PhpMyAdmin\\Config\\Forms\\User\\';
|
||||
}
|
199
admin/phpMyAdmin/libraries/classes/Config/PageSettings.php
Normal file
199
admin/phpMyAdmin/libraries/classes/Config/PageSettings.php
Normal file
|
@ -0,0 +1,199 @@
|
|||
<?php
|
||||
/**
|
||||
* Page-related settings
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config;
|
||||
|
||||
use PhpMyAdmin\Config\Forms\Page\PageFormList;
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\UserPreferences;
|
||||
|
||||
/**
|
||||
* Page-related settings
|
||||
*/
|
||||
class PageSettings
|
||||
{
|
||||
/**
|
||||
* Contains id of the form element
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $elemId = 'page_settings_modal';
|
||||
|
||||
/**
|
||||
* Name of the group to show
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $groupName = '';
|
||||
|
||||
/**
|
||||
* Contains HTML of errors
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $errorHTML = '';
|
||||
|
||||
/**
|
||||
* Contains HTML of settings
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $HTML = '';
|
||||
|
||||
/** @var UserPreferences */
|
||||
private $userPreferences;
|
||||
|
||||
/**
|
||||
* @param string $formGroupName The name of config form group to display
|
||||
* @param string $elemId Id of the div containing settings
|
||||
*/
|
||||
public function __construct($formGroupName, $elemId = null)
|
||||
{
|
||||
$this->userPreferences = new UserPreferences();
|
||||
|
||||
$formClass = PageFormList::get($formGroupName);
|
||||
if ($formClass === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($_REQUEST['printview']) && $_REQUEST['printview'] == '1') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! empty($elemId)) {
|
||||
$this->elemId = $elemId;
|
||||
}
|
||||
$this->groupName = $formGroupName;
|
||||
|
||||
$cf = new ConfigFile($GLOBALS['PMA_Config']->baseSettings);
|
||||
$this->userPreferences->pageInit($cf);
|
||||
|
||||
$formDisplay = new $formClass($cf);
|
||||
|
||||
// Process form
|
||||
$error = null;
|
||||
if (isset($_POST['submit_save'])
|
||||
&& $_POST['submit_save'] == $formGroupName
|
||||
) {
|
||||
$this->processPageSettings($formDisplay, $cf, $error);
|
||||
}
|
||||
|
||||
// Display forms
|
||||
$this->HTML = $this->getPageSettingsDisplay($formDisplay, $error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process response to form
|
||||
*
|
||||
* @param FormDisplay $formDisplay Form
|
||||
* @param ConfigFile $cf Configuration file
|
||||
* @param Message|null $error Error message
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function processPageSettings(&$formDisplay, &$cf, &$error)
|
||||
{
|
||||
if (! $formDisplay->process(false) || $formDisplay->hasErrors()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// save settings
|
||||
$result = $this->userPreferences->save($cf->getConfigArray());
|
||||
if ($result === true) {
|
||||
// reload page
|
||||
$response = Response::getInstance();
|
||||
Core::sendHeaderLocation(
|
||||
$response->getFooter()->getSelfUrl()
|
||||
);
|
||||
exit;
|
||||
}
|
||||
|
||||
$error = $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store errors in _errorHTML
|
||||
*
|
||||
* @param FormDisplay $formDisplay Form
|
||||
* @param Message|null $error Error message
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function storeError(&$formDisplay, &$error)
|
||||
{
|
||||
$retval = '';
|
||||
if ($error) {
|
||||
$retval .= $error->getDisplay();
|
||||
}
|
||||
if ($formDisplay->hasErrors()) {
|
||||
// form has errors
|
||||
$retval .= '<div class="alert alert-danger config-form" role="alert">'
|
||||
. '<b>' . __(
|
||||
'Cannot save settings, submitted configuration form contains '
|
||||
. 'errors!'
|
||||
) . '</b>'
|
||||
. $formDisplay->displayErrors()
|
||||
. '</div>';
|
||||
}
|
||||
$this->errorHTML = $retval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display page-related settings
|
||||
*
|
||||
* @param FormDisplay $formDisplay Form
|
||||
* @param Message $error Error message
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getPageSettingsDisplay(&$formDisplay, &$error)
|
||||
{
|
||||
$response = Response::getInstance();
|
||||
|
||||
$retval = '';
|
||||
|
||||
$this->storeError($formDisplay, $error);
|
||||
|
||||
$retval .= '<div id="' . $this->elemId . '">';
|
||||
$retval .= '<div class="page_settings">';
|
||||
$retval .= $formDisplay->getDisplay(
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
$response->getFooter()->getSelfUrl(),
|
||||
[
|
||||
'submit_save' => $this->groupName,
|
||||
]
|
||||
);
|
||||
$retval .= '</div>';
|
||||
$retval .= '</div>';
|
||||
|
||||
return $retval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get HTML output
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getHTML()
|
||||
{
|
||||
return $this->HTML;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get error HTML output
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getErrorHTML()
|
||||
{
|
||||
return $this->errorHTML;
|
||||
}
|
||||
}
|
568
admin/phpMyAdmin/libraries/classes/Config/ServerConfigChecks.php
Normal file
568
admin/phpMyAdmin/libraries/classes/Config/ServerConfigChecks.php
Normal file
|
@ -0,0 +1,568 @@
|
|||
<?php
|
||||
/**
|
||||
* Server config checks management
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config;
|
||||
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\Sanitize;
|
||||
use PhpMyAdmin\Setup\Index as SetupIndex;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use function count;
|
||||
use function function_exists;
|
||||
use function htmlspecialchars;
|
||||
use function implode;
|
||||
use function ini_get;
|
||||
use function preg_match;
|
||||
use function sprintf;
|
||||
use function strlen;
|
||||
|
||||
/**
|
||||
* Performs various compatibility, security and consistency checks on current config
|
||||
*
|
||||
* Outputs results to message list, must be called between SetupIndex::messagesBegin()
|
||||
* and SetupIndex::messagesEnd()
|
||||
*/
|
||||
class ServerConfigChecks
|
||||
{
|
||||
/** @var ConfigFile configurations being checked */
|
||||
protected $cfg;
|
||||
|
||||
/**
|
||||
* @param ConfigFile $cfg Configuration
|
||||
*/
|
||||
public function __construct(ConfigFile $cfg)
|
||||
{
|
||||
$this->cfg = $cfg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform config checks
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function performConfigChecks()
|
||||
{
|
||||
$blowfishSecret = $this->cfg->get('blowfish_secret');
|
||||
$blowfishSecretSet = false;
|
||||
$cookieAuthUsed = false;
|
||||
|
||||
[$cookieAuthUsed, $blowfishSecret, $blowfishSecretSet]
|
||||
= $this->performConfigChecksServers(
|
||||
$cookieAuthUsed,
|
||||
$blowfishSecret,
|
||||
$blowfishSecretSet
|
||||
);
|
||||
|
||||
$this->performConfigChecksCookieAuthUsed(
|
||||
$cookieAuthUsed,
|
||||
$blowfishSecretSet,
|
||||
$blowfishSecret
|
||||
);
|
||||
|
||||
// $cfg['AllowArbitraryServer']
|
||||
// should be disabled
|
||||
if ($this->cfg->getValue('AllowArbitraryServer')) {
|
||||
$sAllowArbitraryServerWarn = sprintf(
|
||||
__(
|
||||
'This %soption%s should be disabled as it allows attackers to '
|
||||
. 'bruteforce login to any MySQL server. If you feel this is necessary, '
|
||||
. 'use %srestrict login to MySQL server%s or %strusted proxies list%s. '
|
||||
. 'However, IP-based protection with trusted proxies list may not be '
|
||||
. 'reliable if your IP belongs to an ISP where thousands of users, '
|
||||
. 'including you, are connected to.'
|
||||
),
|
||||
'[a@' . Url::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Security]',
|
||||
'[/a]',
|
||||
'[a@' . Url::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Security]',
|
||||
'[/a]',
|
||||
'[a@' . Url::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Security]',
|
||||
'[/a]'
|
||||
);
|
||||
SetupIndex::messagesSet(
|
||||
'notice',
|
||||
'AllowArbitraryServer',
|
||||
Descriptions::get('AllowArbitraryServer'),
|
||||
Sanitize::sanitizeMessage($sAllowArbitraryServerWarn)
|
||||
);
|
||||
}
|
||||
|
||||
$this->performConfigChecksLoginCookie();
|
||||
|
||||
$sDirectoryNotice = __(
|
||||
'This value should be double checked to ensure that this directory is '
|
||||
. 'neither world accessible nor readable or writable by other users on '
|
||||
. 'your server.'
|
||||
);
|
||||
|
||||
// $cfg['SaveDir']
|
||||
// should not be world-accessible
|
||||
if ($this->cfg->getValue('SaveDir') != '') {
|
||||
SetupIndex::messagesSet(
|
||||
'notice',
|
||||
'SaveDir',
|
||||
Descriptions::get('SaveDir'),
|
||||
Sanitize::sanitizeMessage($sDirectoryNotice)
|
||||
);
|
||||
}
|
||||
|
||||
// $cfg['TempDir']
|
||||
// should not be world-accessible
|
||||
if ($this->cfg->getValue('TempDir') != '') {
|
||||
SetupIndex::messagesSet(
|
||||
'notice',
|
||||
'TempDir',
|
||||
Descriptions::get('TempDir'),
|
||||
Sanitize::sanitizeMessage($sDirectoryNotice)
|
||||
);
|
||||
}
|
||||
|
||||
$this->performConfigChecksZips();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check config of servers
|
||||
*
|
||||
* @param bool $cookieAuthUsed Cookie auth is used
|
||||
* @param string $blowfishSecret Blowfish secret
|
||||
* @param bool $blowfishSecretSet Blowfish secret set
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function performConfigChecksServers(
|
||||
$cookieAuthUsed,
|
||||
$blowfishSecret,
|
||||
$blowfishSecretSet
|
||||
) {
|
||||
$serverCnt = $this->cfg->getServerCount();
|
||||
for ($i = 1; $i <= $serverCnt; $i++) {
|
||||
$cookieAuthServer
|
||||
= ($this->cfg->getValue('Servers/' . $i . '/auth_type') === 'cookie');
|
||||
$cookieAuthUsed |= $cookieAuthServer;
|
||||
$serverName = $this->performConfigChecksServersGetServerName(
|
||||
$this->cfg->getServerName($i),
|
||||
$i
|
||||
);
|
||||
$serverName = htmlspecialchars($serverName);
|
||||
|
||||
[$blowfishSecret, $blowfishSecretSet]
|
||||
= $this->performConfigChecksServersSetBlowfishSecret(
|
||||
$blowfishSecret,
|
||||
$cookieAuthServer,
|
||||
$blowfishSecretSet
|
||||
);
|
||||
|
||||
// $cfg['Servers'][$i]['ssl']
|
||||
// should be enabled if possible
|
||||
if (! $this->cfg->getValue('Servers/' . $i . '/ssl')) {
|
||||
$title = Descriptions::get('Servers/1/ssl') . ' (' . $serverName . ')';
|
||||
SetupIndex::messagesSet(
|
||||
'notice',
|
||||
'Servers/' . $i . '/ssl',
|
||||
$title,
|
||||
__(
|
||||
'You should use SSL connections if your database server '
|
||||
. 'supports it.'
|
||||
)
|
||||
);
|
||||
}
|
||||
$sSecurityInfoMsg = Sanitize::sanitizeMessage(sprintf(
|
||||
__(
|
||||
'If you feel this is necessary, use additional protection settings - '
|
||||
. '%1$shost authentication%2$s settings and %3$strusted proxies list%4$s. '
|
||||
. 'However, IP-based protection may not be reliable if your IP belongs '
|
||||
. 'to an ISP where thousands of users, including you, are connected to.'
|
||||
),
|
||||
'[a@' . Url::getCommon(['page' => 'servers', 'mode' => 'edit', 'id' => $i]) . '#tab_Server_config]',
|
||||
'[/a]',
|
||||
'[a@' . Url::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Security]',
|
||||
'[/a]'
|
||||
));
|
||||
|
||||
// $cfg['Servers'][$i]['auth_type']
|
||||
// warn about full user credentials if 'auth_type' is 'config'
|
||||
if ($this->cfg->getValue('Servers/' . $i . '/auth_type') === 'config'
|
||||
&& $this->cfg->getValue('Servers/' . $i . '/user') != ''
|
||||
&& $this->cfg->getValue('Servers/' . $i . '/password') != ''
|
||||
) {
|
||||
$title = Descriptions::get('Servers/1/auth_type')
|
||||
. ' (' . $serverName . ')';
|
||||
SetupIndex::messagesSet(
|
||||
'notice',
|
||||
'Servers/' . $i . '/auth_type',
|
||||
$title,
|
||||
Sanitize::sanitizeMessage(sprintf(
|
||||
__(
|
||||
'You set the [kbd]config[/kbd] authentication type and included '
|
||||
. 'username and password for auto-login, which is not a desirable '
|
||||
. 'option for live hosts. Anyone who knows or guesses your phpMyAdmin '
|
||||
. 'URL can directly access your phpMyAdmin panel. Set %1$sauthentication '
|
||||
. 'type%2$s to [kbd]cookie[/kbd] or [kbd]http[/kbd].'
|
||||
),
|
||||
'[a@' . Url::getCommon(['page' => 'servers', 'mode' => 'edit', 'id' => $i]) . '#tab_Server]',
|
||||
'[/a]'
|
||||
))
|
||||
. ' ' . $sSecurityInfoMsg
|
||||
);
|
||||
}
|
||||
|
||||
// $cfg['Servers'][$i]['AllowRoot']
|
||||
// $cfg['Servers'][$i]['AllowNoPassword']
|
||||
// serious security flaw
|
||||
if (! $this->cfg->getValue('Servers/' . $i . '/AllowRoot')
|
||||
|| ! $this->cfg->getValue('Servers/' . $i . '/AllowNoPassword')
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$title = Descriptions::get('Servers/1/AllowNoPassword')
|
||||
. ' (' . $serverName . ')';
|
||||
SetupIndex::messagesSet(
|
||||
'notice',
|
||||
'Servers/' . $i . '/AllowNoPassword',
|
||||
$title,
|
||||
__('You allow for connecting to the server without a password.')
|
||||
. ' ' . $sSecurityInfoMsg
|
||||
);
|
||||
}
|
||||
|
||||
return [
|
||||
$cookieAuthUsed,
|
||||
$blowfishSecret,
|
||||
$blowfishSecretSet,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set blowfish secret
|
||||
*
|
||||
* @param string|null $blowfishSecret Blowfish secret
|
||||
* @param bool $cookieAuthServer Cookie auth is used
|
||||
* @param bool $blowfishSecretSet Blowfish secret set
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function performConfigChecksServersSetBlowfishSecret(
|
||||
$blowfishSecret,
|
||||
$cookieAuthServer,
|
||||
$blowfishSecretSet
|
||||
): array {
|
||||
if ($cookieAuthServer && $blowfishSecret === null) {
|
||||
$blowfishSecretSet = true;
|
||||
$this->cfg->set('blowfish_secret', Util::generateRandom(32));
|
||||
}
|
||||
|
||||
return [
|
||||
$blowfishSecret,
|
||||
$blowfishSecretSet,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Define server name
|
||||
*
|
||||
* @param string $serverName Server name
|
||||
* @param int $serverId Server id
|
||||
*
|
||||
* @return string Server name
|
||||
*/
|
||||
protected function performConfigChecksServersGetServerName(
|
||||
$serverName,
|
||||
$serverId
|
||||
) {
|
||||
if ($serverName === 'localhost') {
|
||||
return $serverName . ' [' . $serverId . ']';
|
||||
}
|
||||
|
||||
return $serverName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform config checks for zip part.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function performConfigChecksZips()
|
||||
{
|
||||
$this->performConfigChecksServerGZipdump();
|
||||
$this->performConfigChecksServerBZipdump();
|
||||
$this->performConfigChecksServersZipdump();
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform config checks for zip part.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function performConfigChecksServersZipdump()
|
||||
{
|
||||
// $cfg['ZipDump']
|
||||
// requires zip_open in import
|
||||
if ($this->cfg->getValue('ZipDump') && ! $this->functionExists('zip_open')) {
|
||||
SetupIndex::messagesSet(
|
||||
'error',
|
||||
'ZipDump_import',
|
||||
Descriptions::get('ZipDump'),
|
||||
Sanitize::sanitizeMessage(sprintf(
|
||||
__(
|
||||
'%sZip decompression%s requires functions (%s) which are unavailable '
|
||||
. 'on this system.'
|
||||
),
|
||||
'[a@' . Url::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Import_export]',
|
||||
'[/a]',
|
||||
'zip_open'
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
// $cfg['ZipDump']
|
||||
// requires gzcompress in export
|
||||
if (! $this->cfg->getValue('ZipDump') || $this->functionExists('gzcompress')) {
|
||||
return;
|
||||
}
|
||||
|
||||
SetupIndex::messagesSet(
|
||||
'error',
|
||||
'ZipDump_export',
|
||||
Descriptions::get('ZipDump'),
|
||||
Sanitize::sanitizeMessage(sprintf(
|
||||
__(
|
||||
'%sZip compression%s requires functions (%s) which are unavailable on '
|
||||
. 'this system.'
|
||||
),
|
||||
'[a@' . Url::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Import_export]',
|
||||
'[/a]',
|
||||
'gzcompress'
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check config of servers
|
||||
*
|
||||
* @param bool $cookieAuthUsed Cookie auth is used
|
||||
* @param bool $blowfishSecretSet Blowfish secret set
|
||||
* @param string $blowfishSecret Blowfish secret
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function performConfigChecksCookieAuthUsed(
|
||||
$cookieAuthUsed,
|
||||
$blowfishSecretSet,
|
||||
$blowfishSecret
|
||||
) {
|
||||
// $cfg['blowfish_secret']
|
||||
// it's required for 'cookie' authentication
|
||||
if (! $cookieAuthUsed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($blowfishSecretSet) {
|
||||
// 'cookie' auth used, blowfish_secret was generated
|
||||
SetupIndex::messagesSet(
|
||||
'notice',
|
||||
'blowfish_secret_created',
|
||||
Descriptions::get('blowfish_secret'),
|
||||
Sanitize::sanitizeMessage(__(
|
||||
'You didn\'t have blowfish secret set and have enabled '
|
||||
. '[kbd]cookie[/kbd] authentication, so a key was automatically '
|
||||
. 'generated for you. It is used to encrypt cookies; you don\'t need to '
|
||||
. 'remember it.'
|
||||
))
|
||||
);
|
||||
} else {
|
||||
$blowfishWarnings = [];
|
||||
// check length
|
||||
if (strlen($blowfishSecret) < 32) {
|
||||
// too short key
|
||||
$blowfishWarnings[] = __(
|
||||
'Key is too short, it should have at least 32 characters.'
|
||||
);
|
||||
}
|
||||
// check used characters
|
||||
$hasDigits = (bool) preg_match('/\d/', $blowfishSecret);
|
||||
$hasChars = (bool) preg_match('/\S/', $blowfishSecret);
|
||||
$hasNonword = (bool) preg_match('/\W/', $blowfishSecret);
|
||||
if (! $hasDigits || ! $hasChars || ! $hasNonword) {
|
||||
$blowfishWarnings[] = Sanitize::sanitizeMessage(
|
||||
__(
|
||||
'Key should contain letters, numbers [em]and[/em] '
|
||||
. 'special characters.'
|
||||
)
|
||||
);
|
||||
}
|
||||
if (! empty($blowfishWarnings)) {
|
||||
SetupIndex::messagesSet(
|
||||
'error',
|
||||
'blowfish_warnings' . count($blowfishWarnings),
|
||||
Descriptions::get('blowfish_secret'),
|
||||
implode('<br>', $blowfishWarnings)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check configuration for login cookie
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function performConfigChecksLoginCookie()
|
||||
{
|
||||
// $cfg['LoginCookieValidity']
|
||||
// value greater than session.gc_maxlifetime will cause
|
||||
// random session invalidation after that time
|
||||
$loginCookieValidity = $this->cfg->getValue('LoginCookieValidity');
|
||||
if ($loginCookieValidity > ini_get('session.gc_maxlifetime')
|
||||
) {
|
||||
SetupIndex::messagesSet(
|
||||
'error',
|
||||
'LoginCookieValidity',
|
||||
Descriptions::get('LoginCookieValidity'),
|
||||
Sanitize::sanitizeMessage(sprintf(
|
||||
__(
|
||||
'%1$sLogin cookie validity%2$s greater than %3$ssession.gc_maxlifetime%4$s may '
|
||||
. 'cause random session invalidation (currently session.gc_maxlifetime '
|
||||
. 'is %5$d).'
|
||||
),
|
||||
'[a@' . Url::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Security]',
|
||||
'[/a]',
|
||||
'[a@' . Core::getPHPDocLink('session.configuration.php#ini.session.gc-maxlifetime') . ']',
|
||||
'[/a]',
|
||||
ini_get('session.gc_maxlifetime')
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
// $cfg['LoginCookieValidity']
|
||||
// should be at most 1800 (30 min)
|
||||
if ($loginCookieValidity > 1800) {
|
||||
SetupIndex::messagesSet(
|
||||
'notice',
|
||||
'LoginCookieValidity',
|
||||
Descriptions::get('LoginCookieValidity'),
|
||||
Sanitize::sanitizeMessage(sprintf(
|
||||
__(
|
||||
'%sLogin cookie validity%s should be set to 1800 seconds (30 minutes) '
|
||||
. 'at most. Values larger than 1800 may pose a security risk such as '
|
||||
. 'impersonation.'
|
||||
),
|
||||
'[a@' . Url::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Security]',
|
||||
'[/a]'
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
// $cfg['LoginCookieValidity']
|
||||
// $cfg['LoginCookieStore']
|
||||
// LoginCookieValidity must be less or equal to LoginCookieStore
|
||||
if (($this->cfg->getValue('LoginCookieStore') == 0)
|
||||
|| ($loginCookieValidity <= $this->cfg->getValue('LoginCookieStore'))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
SetupIndex::messagesSet(
|
||||
'error',
|
||||
'LoginCookieValidity',
|
||||
Descriptions::get('LoginCookieValidity'),
|
||||
Sanitize::sanitizeMessage(sprintf(
|
||||
__(
|
||||
'If using [kbd]cookie[/kbd] authentication and %sLogin cookie store%s '
|
||||
. 'is not 0, %sLogin cookie validity%s must be set to a value less or '
|
||||
. 'equal to it.'
|
||||
),
|
||||
'[a@' . Url::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Security]',
|
||||
'[/a]',
|
||||
'[a@' . Url::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Security]',
|
||||
'[/a]'
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check GZipDump configuration
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function performConfigChecksServerBZipdump()
|
||||
{
|
||||
// $cfg['BZipDump']
|
||||
// requires bzip2 functions
|
||||
if (! $this->cfg->getValue('BZipDump')
|
||||
|| ($this->functionExists('bzopen') && $this->functionExists('bzcompress'))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$functions = $this->functionExists('bzopen')
|
||||
? '' :
|
||||
'bzopen';
|
||||
$functions .= $this->functionExists('bzcompress')
|
||||
? ''
|
||||
: ($functions ? ', ' : '') . 'bzcompress';
|
||||
SetupIndex::messagesSet(
|
||||
'error',
|
||||
'BZipDump',
|
||||
Descriptions::get('BZipDump'),
|
||||
Sanitize::sanitizeMessage(
|
||||
sprintf(
|
||||
__(
|
||||
'%1$sBzip2 compression and decompression%2$s requires functions (%3$s) which '
|
||||
. 'are unavailable on this system.'
|
||||
),
|
||||
'[a@' . Url::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Import_export]',
|
||||
'[/a]',
|
||||
$functions
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check GZipDump configuration
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function performConfigChecksServerGZipdump()
|
||||
{
|
||||
// $cfg['GZipDump']
|
||||
// requires zlib functions
|
||||
if (! $this->cfg->getValue('GZipDump')
|
||||
|| ($this->functionExists('gzopen') && $this->functionExists('gzencode'))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
SetupIndex::messagesSet(
|
||||
'error',
|
||||
'GZipDump',
|
||||
Descriptions::get('GZipDump'),
|
||||
Sanitize::sanitizeMessage(sprintf(
|
||||
__(
|
||||
'%1$sGZip compression and decompression%2$s requires functions (%3$s) which '
|
||||
. 'are unavailable on this system.'
|
||||
),
|
||||
'[a@' . Url::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Import_export]',
|
||||
'[/a]',
|
||||
'gzencode'
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper around function_exists to allow mock in test
|
||||
*
|
||||
* @param string $name Function name
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function functionExists($name)
|
||||
{
|
||||
return function_exists($name);
|
||||
}
|
||||
}
|
489
admin/phpMyAdmin/libraries/classes/Config/SpecialSchemaLinks.php
Normal file
489
admin/phpMyAdmin/libraries/classes/Config/SpecialSchemaLinks.php
Normal file
|
@ -0,0 +1,489 @@
|
|||
<?php
|
||||
/**
|
||||
* Links configuration for MySQL system tables
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config;
|
||||
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
|
||||
class SpecialSchemaLinks
|
||||
{
|
||||
/**
|
||||
* This array represent the details for generating links inside
|
||||
* special schemas like mysql, information_schema etc.
|
||||
* Major element represent a schema.
|
||||
* All the strings in this array represented in lower case
|
||||
*
|
||||
* Array structure ex:
|
||||
* array(
|
||||
* // Database name is the major element
|
||||
* 'mysql' => array(
|
||||
* // Table name
|
||||
* 'db' => array(
|
||||
* // Column name
|
||||
* 'user' => array(
|
||||
* // Main url param (can be an array where represent sql)
|
||||
* 'link_param' => 'username',
|
||||
* // Other url params
|
||||
* 'link_dependancy_params' => array(
|
||||
* 0 => array(
|
||||
* // URL parameter name
|
||||
* // (can be array where url param has static value)
|
||||
* 'param_info' => 'hostname',
|
||||
* // Column name related to url param
|
||||
* 'column_name' => 'host'
|
||||
* )
|
||||
* ),
|
||||
* // Page to link
|
||||
* 'default_page' => './' . Url::getFromRoute('/server/privileges')
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* );
|
||||
*
|
||||
* @return array<string,array<string,array<string,array<string,array<int,array<string,string>>|string>>>>
|
||||
*
|
||||
* @phpstan-return array<
|
||||
* string, array<
|
||||
* string, array<
|
||||
* string,
|
||||
* array{
|
||||
* 'link_param': string,
|
||||
* 'link_dependancy_params'?: array<
|
||||
* int,
|
||||
* array{'param_info': string, 'column_name': string}
|
||||
* >,
|
||||
* 'default_page': string
|
||||
* }>
|
||||
* >
|
||||
* >
|
||||
* }
|
||||
*/
|
||||
public static function get(): array
|
||||
{
|
||||
global $cfg;
|
||||
|
||||
$defaultPage = './' . Util::getScriptNameForOption(
|
||||
$cfg['DefaultTabTable'],
|
||||
'table'
|
||||
);
|
||||
|
||||
return [
|
||||
'mysql' => [
|
||||
'columns_priv' => [
|
||||
'user' => [
|
||||
'link_param' => 'username',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'hostname',
|
||||
'column_name' => 'host',
|
||||
],
|
||||
],
|
||||
'default_page' => './' . Url::getFromRoute('/server/privileges'),
|
||||
],
|
||||
'table_name' => [
|
||||
'link_param' => 'table',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'Db',
|
||||
],
|
||||
],
|
||||
'default_page' => $defaultPage,
|
||||
],
|
||||
'column_name' => [
|
||||
'link_param' => 'field',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'Db',
|
||||
],
|
||||
1 => [
|
||||
'param_info' => 'table',
|
||||
'column_name' => 'Table_name',
|
||||
],
|
||||
],
|
||||
'default_page' => './' . Url::getFromRoute('/table/structure/change', ['change_column' => 1]),
|
||||
],
|
||||
],
|
||||
'db' => [
|
||||
'user' => [
|
||||
'link_param' => 'username',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'hostname',
|
||||
'column_name' => 'host',
|
||||
],
|
||||
],
|
||||
'default_page' => './' . Url::getFromRoute('/server/privileges'),
|
||||
],
|
||||
],
|
||||
'event' => [
|
||||
'name' => [
|
||||
'link_param' => 'item_name',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'db',
|
||||
],
|
||||
],
|
||||
'default_page' => './' . Url::getFromRoute('/database/events', ['edit_item' => 1]),
|
||||
],
|
||||
|
||||
],
|
||||
'innodb_index_stats' => [
|
||||
'table_name' => [
|
||||
'link_param' => 'table',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'database_name',
|
||||
],
|
||||
],
|
||||
'default_page' => $defaultPage,
|
||||
],
|
||||
'index_name' => [
|
||||
'link_param' => 'index',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'database_name',
|
||||
],
|
||||
1 => [
|
||||
'param_info' => 'table',
|
||||
'column_name' => 'table_name',
|
||||
],
|
||||
],
|
||||
'default_page' => './' . Url::getFromRoute('/table/structure'),
|
||||
],
|
||||
],
|
||||
'innodb_table_stats' => [
|
||||
'table_name' => [
|
||||
'link_param' => 'table',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'database_name',
|
||||
],
|
||||
],
|
||||
'default_page' => $defaultPage,
|
||||
],
|
||||
],
|
||||
'proc' => [
|
||||
'name' => [
|
||||
'link_param' => 'item_name',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'db',
|
||||
],
|
||||
1 => [
|
||||
'param_info' => 'item_type',
|
||||
'column_name' => 'type',
|
||||
],
|
||||
],
|
||||
'default_page' => './' . Url::getFromRoute('/database/routines', ['edit_item' => 1]),
|
||||
],
|
||||
'specific_name' => [
|
||||
'link_param' => 'item_name',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'db',
|
||||
],
|
||||
1 => [
|
||||
'param_info' => 'item_type',
|
||||
'column_name' => 'type',
|
||||
],
|
||||
],
|
||||
'default_page' => './' . Url::getFromRoute('/database/routines', ['edit_item' => 1]),
|
||||
],
|
||||
],
|
||||
'proc_priv' => [
|
||||
'user' => [
|
||||
'link_param' => 'username',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'hostname',
|
||||
'column_name' => 'Host',
|
||||
],
|
||||
],
|
||||
'default_page' => './' . Url::getFromRoute('/server/privileges'),
|
||||
],
|
||||
'routine_name' => [
|
||||
'link_param' => 'item_name',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'Db',
|
||||
],
|
||||
1 => [
|
||||
'param_info' => 'item_type',
|
||||
'column_name' => 'Routine_type',
|
||||
],
|
||||
],
|
||||
'default_page' => './' . Url::getFromRoute('/database/routines', ['edit_item' => 1]),
|
||||
],
|
||||
],
|
||||
'proxies_priv' => [
|
||||
'user' => [
|
||||
'link_param' => 'username',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'hostname',
|
||||
'column_name' => 'Host',
|
||||
],
|
||||
],
|
||||
'default_page' => './' . Url::getFromRoute('/server/privileges'),
|
||||
],
|
||||
],
|
||||
'tables_priv' => [
|
||||
'user' => [
|
||||
'link_param' => 'username',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'hostname',
|
||||
'column_name' => 'Host',
|
||||
],
|
||||
],
|
||||
'default_page' => './' . Url::getFromRoute('/server/privileges'),
|
||||
],
|
||||
'table_name' => [
|
||||
'link_param' => 'table',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'Db',
|
||||
],
|
||||
],
|
||||
'default_page' => $defaultPage,
|
||||
],
|
||||
],
|
||||
'user' => [
|
||||
'user' => [
|
||||
'link_param' => 'username',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'hostname',
|
||||
'column_name' => 'host',
|
||||
],
|
||||
],
|
||||
'default_page' => './' . Url::getFromRoute('/server/privileges'),
|
||||
],
|
||||
],
|
||||
],
|
||||
'information_schema' => [
|
||||
'columns' => [
|
||||
'table_name' => [
|
||||
'link_param' => 'table',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'table_schema',
|
||||
],
|
||||
],
|
||||
'default_page' => $defaultPage,
|
||||
],
|
||||
'column_name' => [
|
||||
'link_param' => 'field',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'table_schema',
|
||||
],
|
||||
1 => [
|
||||
'param_info' => 'table',
|
||||
'column_name' => 'table_name',
|
||||
],
|
||||
],
|
||||
'default_page' => './' . Url::getFromRoute('/table/structure/change', ['change_column' => 1]),
|
||||
],
|
||||
],
|
||||
'key_column_usage' => [
|
||||
'table_name' => [
|
||||
'link_param' => 'table',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'constraint_schema',
|
||||
],
|
||||
],
|
||||
'default_page' => $defaultPage,
|
||||
],
|
||||
'column_name' => [
|
||||
'link_param' => 'field',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'table_schema',
|
||||
],
|
||||
1 => [
|
||||
'param_info' => 'table',
|
||||
'column_name' => 'table_name',
|
||||
],
|
||||
],
|
||||
'default_page' => './' . Url::getFromRoute('/table/structure/change', ['change_column' => 1]),
|
||||
],
|
||||
'referenced_table_name' => [
|
||||
'link_param' => 'table',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'referenced_table_schema',
|
||||
],
|
||||
],
|
||||
'default_page' => $defaultPage,
|
||||
],
|
||||
'referenced_column_name' => [
|
||||
'link_param' => 'field',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'referenced_table_schema',
|
||||
],
|
||||
1 => [
|
||||
'param_info' => 'table',
|
||||
'column_name' => 'referenced_table_name',
|
||||
],
|
||||
],
|
||||
'default_page' => './' . Url::getFromRoute('/table/structure/change', ['change_column' => 1]),
|
||||
],
|
||||
],
|
||||
'partitions' => [
|
||||
'table_name' => [
|
||||
'link_param' => 'table',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'table_schema',
|
||||
],
|
||||
],
|
||||
'default_page' => $defaultPage,
|
||||
],
|
||||
],
|
||||
'processlist' => [
|
||||
'user' => [
|
||||
'link_param' => 'username',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'hostname',
|
||||
'column_name' => 'host',
|
||||
],
|
||||
],
|
||||
'default_page' => './' . Url::getFromRoute('/server/privileges'),
|
||||
],
|
||||
],
|
||||
'referential_constraints' => [
|
||||
'table_name' => [
|
||||
'link_param' => 'table',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'constraint_schema',
|
||||
],
|
||||
],
|
||||
'default_page' => $defaultPage,
|
||||
],
|
||||
'referenced_table_name' => [
|
||||
'link_param' => 'table',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'constraint_schema',
|
||||
],
|
||||
],
|
||||
'default_page' => $defaultPage,
|
||||
],
|
||||
],
|
||||
'routines' => [
|
||||
'routine_name' => [
|
||||
'link_param' => 'item_name',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'routine_schema',
|
||||
],
|
||||
1 => [
|
||||
'param_info' => 'item_type',
|
||||
'column_name' => 'routine_type',
|
||||
],
|
||||
],
|
||||
'default_page' => './' . Url::getFromRoute('/database/routines'),
|
||||
],
|
||||
],
|
||||
'schemata' => [
|
||||
'schema_name' => [
|
||||
'link_param' => 'db',
|
||||
'default_page' => $defaultPage,
|
||||
],
|
||||
],
|
||||
'statistics' => [
|
||||
'table_name' => [
|
||||
'link_param' => 'table',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'table_schema',
|
||||
],
|
||||
],
|
||||
'default_page' => $defaultPage,
|
||||
],
|
||||
'column_name' => [
|
||||
'link_param' => 'field',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'table_schema',
|
||||
],
|
||||
1 => [
|
||||
'param_info' => 'table',
|
||||
'column_name' => 'table_name',
|
||||
],
|
||||
],
|
||||
'default_page' => './' . Url::getFromRoute('/table/structure/change', ['change_column' => 1]),
|
||||
],
|
||||
],
|
||||
'tables' => [
|
||||
'table_name' => [
|
||||
'link_param' => 'table',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'table_schema',
|
||||
],
|
||||
],
|
||||
'default_page' => $defaultPage,
|
||||
],
|
||||
],
|
||||
'table_constraints' => [
|
||||
'table_name' => [
|
||||
'link_param' => 'table',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'table_schema',
|
||||
],
|
||||
],
|
||||
'default_page' => $defaultPage,
|
||||
],
|
||||
],
|
||||
'views' => [
|
||||
'table_name' => [
|
||||
'link_param' => 'table',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'table_schema',
|
||||
],
|
||||
],
|
||||
'default_page' => $defaultPage,
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
608
admin/phpMyAdmin/libraries/classes/Config/Validator.php
Normal file
608
admin/phpMyAdmin/libraries/classes/Config/Validator.php
Normal file
|
@ -0,0 +1,608 @@
|
|||
<?php
|
||||
/**
|
||||
* Form validation for configuration editor
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config;
|
||||
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\Util;
|
||||
use function mysqli_report;
|
||||
use const FILTER_FLAG_IPV4;
|
||||
use const FILTER_FLAG_IPV6;
|
||||
use const FILTER_VALIDATE_IP;
|
||||
use const MYSQLI_REPORT_OFF;
|
||||
use const PHP_INT_MAX;
|
||||
use function array_map;
|
||||
use function array_merge;
|
||||
use function array_shift;
|
||||
use function call_user_func_array;
|
||||
use function count;
|
||||
use function error_clear_last;
|
||||
use function error_get_last;
|
||||
use function explode;
|
||||
use function filter_var;
|
||||
use function htmlspecialchars;
|
||||
use function intval;
|
||||
use function is_array;
|
||||
use function is_object;
|
||||
use function mb_strpos;
|
||||
use function mb_substr;
|
||||
use function mysqli_close;
|
||||
use function mysqli_connect;
|
||||
use function preg_match;
|
||||
use function preg_replace;
|
||||
use function sprintf;
|
||||
use function str_replace;
|
||||
use function trim;
|
||||
|
||||
/**
|
||||
* Validation class for various validation functions
|
||||
*
|
||||
* Validation function takes two argument: id for which it is called
|
||||
* and array of fields' values (usually values for entire formset).
|
||||
* The function must always return an array with an error (or error array)
|
||||
* assigned to a form element (formset name or field path). Even if there are
|
||||
* no errors, key must be set with an empty value.
|
||||
*
|
||||
* Validation functions are assigned in $cfg_db['_validators'] (config.values.php).
|
||||
*/
|
||||
class Validator
|
||||
{
|
||||
/**
|
||||
* Returns validator list
|
||||
*
|
||||
* @param ConfigFile $cf Config file instance
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getValidators(ConfigFile $cf)
|
||||
{
|
||||
static $validators = null;
|
||||
|
||||
if ($validators !== null) {
|
||||
return $validators;
|
||||
}
|
||||
|
||||
$validators = $cf->getDbEntry('_validators', []);
|
||||
if ($GLOBALS['PMA_Config']->get('is_setup')) {
|
||||
return $validators;
|
||||
}
|
||||
|
||||
// not in setup script: load additional validators for user
|
||||
// preferences we need original config values not overwritten
|
||||
// by user preferences, creating a new PhpMyAdmin\Config instance is a
|
||||
// better idea than hacking into its code
|
||||
$uvs = $cf->getDbEntry('_userValidators', []);
|
||||
foreach ($uvs as $field => $uvList) {
|
||||
$uvList = (array) $uvList;
|
||||
foreach ($uvList as &$uv) {
|
||||
if (! is_array($uv)) {
|
||||
continue;
|
||||
}
|
||||
for ($i = 1, $nb = count($uv); $i < $nb; $i++) {
|
||||
if (mb_substr($uv[$i], 0, 6) !== 'value:') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$uv[$i] = Core::arrayRead(
|
||||
mb_substr($uv[$i], 6),
|
||||
$GLOBALS['PMA_Config']->baseSettings
|
||||
);
|
||||
}
|
||||
}
|
||||
$validators[$field] = isset($validators[$field])
|
||||
? array_merge((array) $validators[$field], $uvList)
|
||||
: $uvList;
|
||||
}
|
||||
|
||||
return $validators;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs validation $validator_id on values $values and returns error list.
|
||||
*
|
||||
* Return values:
|
||||
* o array, keys - field path or formset id, values - array of errors
|
||||
* when $isPostSource is true values is an empty array to allow for error list
|
||||
* cleanup in HTML document
|
||||
* o false - when no validators match name(s) given by $validator_id
|
||||
*
|
||||
* @param ConfigFile $cf Config file instance
|
||||
* @param string|array $validatorId ID of validator(s) to run
|
||||
* @param array $values Values to validate
|
||||
* @param bool $isPostSource tells whether $values are directly from
|
||||
* POST request
|
||||
*
|
||||
* @return bool|array
|
||||
*/
|
||||
public static function validate(
|
||||
ConfigFile $cf,
|
||||
$validatorId,
|
||||
array &$values,
|
||||
$isPostSource
|
||||
) {
|
||||
// find validators
|
||||
$validatorId = (array) $validatorId;
|
||||
$validators = static::getValidators($cf);
|
||||
$vids = [];
|
||||
foreach ($validatorId as &$vid) {
|
||||
$vid = $cf->getCanonicalPath($vid);
|
||||
if (! isset($validators[$vid])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$vids[] = $vid;
|
||||
}
|
||||
if (empty($vids)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// create argument list with canonical paths and remember path mapping
|
||||
$arguments = [];
|
||||
$keyMap = [];
|
||||
foreach ($values as $k => $v) {
|
||||
$k2 = $isPostSource ? str_replace('-', '/', $k) : $k;
|
||||
$k2 = mb_strpos($k2, '/')
|
||||
? $cf->getCanonicalPath($k2)
|
||||
: $k2;
|
||||
$keyMap[$k2] = $k;
|
||||
$arguments[$k2] = $v;
|
||||
}
|
||||
|
||||
// validate
|
||||
$result = [];
|
||||
foreach ($vids as $vid) {
|
||||
// call appropriate validation functions
|
||||
foreach ((array) $validators[$vid] as $validator) {
|
||||
$vdef = (array) $validator;
|
||||
$vname = array_shift($vdef);
|
||||
$vname = 'PhpMyAdmin\Config\Validator::' . $vname;
|
||||
$args = array_merge([$vid, &$arguments], $vdef);
|
||||
$r = call_user_func_array($vname, $args);
|
||||
|
||||
// merge results
|
||||
if (! is_array($r)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($r as $key => $errorList) {
|
||||
// skip empty values if $isPostSource is false
|
||||
if (! $isPostSource && empty($errorList)) {
|
||||
continue;
|
||||
}
|
||||
if (! isset($result[$key])) {
|
||||
$result[$key] = [];
|
||||
}
|
||||
$result[$key] = array_merge(
|
||||
$result[$key],
|
||||
(array) $errorList
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// restore original paths
|
||||
$newResult = [];
|
||||
foreach ($result as $k => $v) {
|
||||
$k2 = $keyMap[$k] ?? $k;
|
||||
if (is_array($v)) {
|
||||
$newResult[$k2] = array_map('htmlspecialchars', $v);
|
||||
} else {
|
||||
$newResult[$k2] = htmlspecialchars($v);
|
||||
}
|
||||
}
|
||||
|
||||
return empty($newResult) ? true : $newResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test database connection
|
||||
*
|
||||
* @param string $host host name
|
||||
* @param string $port tcp port to use
|
||||
* @param string $socket socket to use
|
||||
* @param string $user username to use
|
||||
* @param string $pass password to use
|
||||
* @param string $errorKey key to use in return array
|
||||
*
|
||||
* @return bool|array
|
||||
*/
|
||||
public static function testDBConnection(
|
||||
$host,
|
||||
$port,
|
||||
$socket,
|
||||
$user,
|
||||
$pass = null,
|
||||
$errorKey = 'Server'
|
||||
) {
|
||||
if ($GLOBALS['cfg']['DBG']['demo']) {
|
||||
// Connection test disabled on the demo server!
|
||||
return true;
|
||||
}
|
||||
|
||||
$error = null;
|
||||
$host = Core::sanitizeMySQLHost($host);
|
||||
|
||||
error_clear_last();
|
||||
|
||||
$socket = empty($socket) ? null : $socket;
|
||||
$port = empty($port) ? null : $port;
|
||||
|
||||
mysqli_report(MYSQLI_REPORT_OFF);
|
||||
|
||||
$conn = @mysqli_connect($host, $user, (string) $pass, '', $port, (string) $socket);
|
||||
if (! $conn) {
|
||||
$error = __('Could not connect to the database server!');
|
||||
} else {
|
||||
mysqli_close($conn);
|
||||
}
|
||||
if ($error !== null) {
|
||||
$lastError = error_get_last();
|
||||
if ($lastError !== null) {
|
||||
$error .= ' - ' . $lastError['message'];
|
||||
}
|
||||
}
|
||||
|
||||
return $error === null ? true : [$errorKey => $error];
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate server config
|
||||
*
|
||||
* @param string $path path to config, not used
|
||||
* keep this parameter since the method is invoked using
|
||||
* reflection along with other similar methods
|
||||
* @param array $values config values
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function validateServer($path, array $values)
|
||||
{
|
||||
$result = [
|
||||
'Server' => '',
|
||||
'Servers/1/user' => '',
|
||||
'Servers/1/SignonSession' => '',
|
||||
'Servers/1/SignonURL' => '',
|
||||
];
|
||||
$error = false;
|
||||
if (empty($values['Servers/1/auth_type'])) {
|
||||
$values['Servers/1/auth_type'] = '';
|
||||
$result['Servers/1/auth_type'] = __('Invalid authentication type!');
|
||||
$error = true;
|
||||
}
|
||||
if ($values['Servers/1/auth_type'] === 'config'
|
||||
&& empty($values['Servers/1/user'])
|
||||
) {
|
||||
$result['Servers/1/user'] = __(
|
||||
'Empty username while using [kbd]config[/kbd] authentication method!'
|
||||
);
|
||||
$error = true;
|
||||
}
|
||||
if ($values['Servers/1/auth_type'] === 'signon'
|
||||
&& empty($values['Servers/1/SignonSession'])
|
||||
) {
|
||||
$result['Servers/1/SignonSession'] = __(
|
||||
'Empty signon session name '
|
||||
. 'while using [kbd]signon[/kbd] authentication method!'
|
||||
);
|
||||
$error = true;
|
||||
}
|
||||
if ($values['Servers/1/auth_type'] === 'signon'
|
||||
&& empty($values['Servers/1/SignonURL'])
|
||||
) {
|
||||
$result['Servers/1/SignonURL'] = __(
|
||||
'Empty signon URL while using [kbd]signon[/kbd] authentication '
|
||||
. 'method!'
|
||||
);
|
||||
$error = true;
|
||||
}
|
||||
|
||||
if (! $error && $values['Servers/1/auth_type'] === 'config') {
|
||||
$password = '';
|
||||
if (! empty($values['Servers/1/password'])) {
|
||||
$password = $values['Servers/1/password'];
|
||||
}
|
||||
$test = static::testDBConnection(
|
||||
empty($values['Servers/1/host']) ? '' : $values['Servers/1/host'],
|
||||
empty($values['Servers/1/port']) ? '' : $values['Servers/1/port'],
|
||||
empty($values['Servers/1/socket']) ? '' : $values['Servers/1/socket'],
|
||||
empty($values['Servers/1/user']) ? '' : $values['Servers/1/user'],
|
||||
$password,
|
||||
'Server'
|
||||
);
|
||||
|
||||
if ($test !== true) {
|
||||
$result = array_merge($result, $test);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate pmadb config
|
||||
*
|
||||
* @param string $path path to config, not used
|
||||
* keep this parameter since the method is invoked using
|
||||
* reflection along with other similar methods
|
||||
* @param array $values config values
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function validatePMAStorage($path, array $values)
|
||||
{
|
||||
$result = [
|
||||
'Server_pmadb' => '',
|
||||
'Servers/1/controluser' => '',
|
||||
'Servers/1/controlpass' => '',
|
||||
];
|
||||
$error = false;
|
||||
|
||||
if (empty($values['Servers/1/pmadb'])) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$result = [];
|
||||
if (empty($values['Servers/1/controluser'])) {
|
||||
$result['Servers/1/controluser'] = __(
|
||||
'Empty phpMyAdmin control user while using phpMyAdmin configuration '
|
||||
. 'storage!'
|
||||
);
|
||||
$error = true;
|
||||
}
|
||||
if (empty($values['Servers/1/controlpass'])) {
|
||||
$result['Servers/1/controlpass'] = __(
|
||||
'Empty phpMyAdmin control user password while using phpMyAdmin '
|
||||
. 'configuration storage!'
|
||||
);
|
||||
$error = true;
|
||||
}
|
||||
if (! $error) {
|
||||
$test = static::testDBConnection(
|
||||
empty($values['Servers/1/host']) ? '' : $values['Servers/1/host'],
|
||||
empty($values['Servers/1/port']) ? '' : $values['Servers/1/port'],
|
||||
empty($values['Servers/1/socket']) ? '' : $values['Servers/1/socket'],
|
||||
empty($values['Servers/1/controluser']) ? '' : $values['Servers/1/controluser'],
|
||||
empty($values['Servers/1/controlpass']) ? '' : $values['Servers/1/controlpass'],
|
||||
'Server_pmadb'
|
||||
);
|
||||
if ($test !== true) {
|
||||
$result = array_merge($result, $test);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates regular expression
|
||||
*
|
||||
* @param string $path path to config
|
||||
* @param array $values config values
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function validateRegex($path, array $values)
|
||||
{
|
||||
$result = [$path => ''];
|
||||
|
||||
if (empty($values[$path])) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
error_clear_last();
|
||||
|
||||
$matches = [];
|
||||
// in libraries/ListDatabase.php _checkHideDatabase(),
|
||||
// a '/' is used as the delimiter for hide_db
|
||||
@preg_match('/' . Util::requestString($values[$path]) . '/', '', $matches);
|
||||
|
||||
$currentError = error_get_last();
|
||||
|
||||
if ($currentError !== null) {
|
||||
$error = preg_replace('/^preg_match\(\): /', '', $currentError['message']);
|
||||
|
||||
return [$path => $error];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates TrustedProxies field
|
||||
*
|
||||
* @param string $path path to config
|
||||
* @param array $values config values
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function validateTrustedProxies($path, array $values)
|
||||
{
|
||||
$result = [$path => []];
|
||||
|
||||
if (empty($values[$path])) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
if (is_array($values[$path]) || is_object($values[$path])) {
|
||||
// value already processed by FormDisplay::save
|
||||
$lines = [];
|
||||
foreach ($values[$path] as $ip => $v) {
|
||||
$v = Util::requestString($v);
|
||||
$lines[] = preg_match('/^-\d+$/', $ip)
|
||||
? $v
|
||||
: $ip . ': ' . $v;
|
||||
}
|
||||
} else {
|
||||
// AJAX validation
|
||||
$lines = explode("\n", $values[$path]);
|
||||
}
|
||||
foreach ($lines as $line) {
|
||||
$line = trim($line);
|
||||
$matches = [];
|
||||
// we catch anything that may (or may not) be an IP
|
||||
if (! preg_match('/^(.+):(?:[ ]?)\\w+$/', $line, $matches)) {
|
||||
$result[$path][] = __('Incorrect value:') . ' '
|
||||
. htmlspecialchars($line);
|
||||
continue;
|
||||
}
|
||||
// now let's check whether we really have an IP address
|
||||
if (filter_var($matches[1], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) === false
|
||||
&& filter_var($matches[1], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) === false
|
||||
) {
|
||||
$ip = htmlspecialchars(trim($matches[1]));
|
||||
$result[$path][] = sprintf(__('Incorrect IP address: %s'), $ip);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests integer value
|
||||
*
|
||||
* @param string $path path to config
|
||||
* @param array $values config values
|
||||
* @param bool $allowNegative allow negative values
|
||||
* @param bool $allowZero allow zero
|
||||
* @param int $maxValue max allowed value
|
||||
* @param string $errorString error message string
|
||||
*
|
||||
* @return string empty string if test is successful
|
||||
*/
|
||||
public static function validateNumber(
|
||||
$path,
|
||||
array $values,
|
||||
$allowNegative,
|
||||
$allowZero,
|
||||
$maxValue,
|
||||
$errorString
|
||||
) {
|
||||
if (empty($values[$path])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$value = Util::requestString($values[$path]);
|
||||
|
||||
if (intval($value) != $value
|
||||
|| (! $allowNegative && $value < 0)
|
||||
|| (! $allowZero && $value == 0)
|
||||
|| $value > $maxValue
|
||||
) {
|
||||
return $errorString;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates port number
|
||||
*
|
||||
* @param string $path path to config
|
||||
* @param array $values config values
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function validatePortNumber($path, array $values)
|
||||
{
|
||||
return [
|
||||
$path => static::validateNumber(
|
||||
$path,
|
||||
$values,
|
||||
false,
|
||||
false,
|
||||
65535,
|
||||
__('Not a valid port number!')
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates positive number
|
||||
*
|
||||
* @param string $path path to config
|
||||
* @param array $values config values
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function validatePositiveNumber($path, array $values)
|
||||
{
|
||||
return [
|
||||
$path => static::validateNumber(
|
||||
$path,
|
||||
$values,
|
||||
false,
|
||||
false,
|
||||
PHP_INT_MAX,
|
||||
__('Not a positive number!')
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates non-negative number
|
||||
*
|
||||
* @param string $path path to config
|
||||
* @param array $values config values
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function validateNonNegativeNumber($path, array $values)
|
||||
{
|
||||
return [
|
||||
$path => static::validateNumber(
|
||||
$path,
|
||||
$values,
|
||||
false,
|
||||
true,
|
||||
PHP_INT_MAX,
|
||||
__('Not a non-negative number!')
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates value according to given regular expression
|
||||
* Pattern and modifiers must be a valid for PCRE <b>and</b> JavaScript RegExp
|
||||
*
|
||||
* @param string $path path to config
|
||||
* @param array $values config values
|
||||
* @param string $regex regular expression to match
|
||||
*
|
||||
* @return array|string
|
||||
*/
|
||||
public static function validateByRegex($path, array $values, $regex)
|
||||
{
|
||||
if (! isset($values[$path])) {
|
||||
return '';
|
||||
}
|
||||
$result = preg_match($regex, Util::requestString($values[$path]));
|
||||
|
||||
return [$path => $result ? '' : __('Incorrect value!')];
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates upper bound for numeric inputs
|
||||
*
|
||||
* @param string $path path to config
|
||||
* @param array $values config values
|
||||
* @param int $maxValue maximal allowed value
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function validateUpperBound($path, array $values, $maxValue)
|
||||
{
|
||||
$result = $values[$path] <= $maxValue;
|
||||
|
||||
return [
|
||||
$path => $result ? '' : sprintf(
|
||||
__('Value must be less than or equal to %s!'),
|
||||
$maxValue
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
148
admin/phpMyAdmin/libraries/classes/Console.php
Normal file
148
admin/phpMyAdmin/libraries/classes/Console.php
Normal file
|
@ -0,0 +1,148 @@
|
|||
<?php
|
||||
/**
|
||||
* Used to render the console of PMA's pages
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin;
|
||||
|
||||
use function count;
|
||||
use function sprintf;
|
||||
|
||||
/**
|
||||
* Class used to output the console
|
||||
*/
|
||||
class Console
|
||||
{
|
||||
/**
|
||||
* Whether to display anything
|
||||
*
|
||||
* @access private
|
||||
* @var bool
|
||||
*/
|
||||
private $isEnabled;
|
||||
|
||||
/**
|
||||
* Whether we are servicing an ajax request.
|
||||
*
|
||||
* @access private
|
||||
* @var bool
|
||||
*/
|
||||
private $isAjax;
|
||||
|
||||
/** @var Relation */
|
||||
private $relation;
|
||||
|
||||
/** @var Template */
|
||||
public $template;
|
||||
|
||||
/**
|
||||
* Creates a new class instance
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
global $dbi;
|
||||
|
||||
$this->isEnabled = true;
|
||||
$this->relation = new Relation($dbi);
|
||||
$this->template = new Template();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the ajax flag to indicate whether
|
||||
* we are servicing an ajax request
|
||||
*
|
||||
* @param bool $isAjax Whether we are servicing an ajax request
|
||||
*/
|
||||
public function setAjax(bool $isAjax): void
|
||||
{
|
||||
$this->isAjax = $isAjax;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables the rendering of the footer
|
||||
*/
|
||||
public function disable(): void
|
||||
{
|
||||
$this->isEnabled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the bookmark content
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public static function getBookmarkContent(): string
|
||||
{
|
||||
global $dbi;
|
||||
|
||||
$template = new Template();
|
||||
$cfgBookmark = Bookmark::getParams($GLOBALS['cfg']['Server']['user']);
|
||||
if ($cfgBookmark) {
|
||||
$bookmarks = Bookmark::getList(
|
||||
$dbi,
|
||||
$GLOBALS['cfg']['Server']['user']
|
||||
);
|
||||
$count_bookmarks = count($bookmarks);
|
||||
if ($count_bookmarks > 0) {
|
||||
$welcomeMessage = sprintf(
|
||||
_ngettext(
|
||||
'Showing %1$d bookmark (both private and shared)',
|
||||
'Showing %1$d bookmarks (both private and shared)',
|
||||
$count_bookmarks
|
||||
),
|
||||
$count_bookmarks
|
||||
);
|
||||
} else {
|
||||
$welcomeMessage = __('No bookmarks');
|
||||
}
|
||||
|
||||
return $template->render('console/bookmark_content', [
|
||||
'welcome_message' => $welcomeMessage,
|
||||
'bookmarks' => $bookmarks,
|
||||
]);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of JS scripts required by console
|
||||
*
|
||||
* @return array list of scripts
|
||||
*/
|
||||
public function getScripts(): array
|
||||
{
|
||||
return ['console.js'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the console
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function getDisplay(): string
|
||||
{
|
||||
if (! $this->isAjax && $this->isEnabled) {
|
||||
$cfgBookmark = Bookmark::getParams(
|
||||
$GLOBALS['cfg']['Server']['user']
|
||||
);
|
||||
|
||||
$image = Html\Generator::getImage('console', __('SQL Query Console'));
|
||||
$_sql_history = $this->relation->getHistory(
|
||||
$GLOBALS['cfg']['Server']['user']
|
||||
);
|
||||
$bookmarkContent = static::getBookmarkContent();
|
||||
|
||||
return $this->template->render('console/display', [
|
||||
'cfg_bookmark' => $cfgBookmark,
|
||||
'image' => $image,
|
||||
'sql_history' => $_sql_history,
|
||||
'bookmark_content' => $bookmarkContent,
|
||||
]);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers;
|
||||
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use function strlen;
|
||||
|
||||
abstract class AbstractController
|
||||
{
|
||||
/** @var Response */
|
||||
protected $response;
|
||||
|
||||
/** @var Template */
|
||||
protected $template;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
*/
|
||||
public function __construct($response, Template $template)
|
||||
{
|
||||
$this->response = $response;
|
||||
$this->template = $template;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $templateData
|
||||
*/
|
||||
protected function render(string $templatePath, array $templateData = []): void
|
||||
{
|
||||
$this->response->addHTML($this->template->render($templatePath, $templateData));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $files
|
||||
*/
|
||||
protected function addScriptFiles(array $files): void
|
||||
{
|
||||
$header = $this->response->getHeader();
|
||||
$scripts = $header->getScripts();
|
||||
$scripts->addFiles($files);
|
||||
}
|
||||
|
||||
protected function hasDatabase(): bool
|
||||
{
|
||||
global $db, $is_db, $errno, $dbi, $message;
|
||||
|
||||
if (isset($is_db) && $is_db) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$is_db = false;
|
||||
if (strlen($db) > 0) {
|
||||
$is_db = $dbi->selectDb($db);
|
||||
// This "Command out of sync" 2014 error may happen, for example
|
||||
// after calling a MySQL procedure; at this point we can't select
|
||||
// the db but it's not necessarily wrong
|
||||
if ($dbi->getError() && $errno == 2014) {
|
||||
$is_db = true;
|
||||
unset($errno);
|
||||
}
|
||||
}
|
||||
|
||||
if (strlen($db) === 0 || ! $is_db) {
|
||||
if ($this->response->isAjax()) {
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON(
|
||||
'message',
|
||||
Message::error(__('No databases selected.'))
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Not a valid db name -> back to the welcome page
|
||||
$params = ['reload' => '1'];
|
||||
if (isset($message)) {
|
||||
$params['message'] = $message;
|
||||
}
|
||||
$uri = './index.php?route=/' . Url::getCommonRaw($params, '&');
|
||||
Core::sendHeaderLocation($uri);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return $is_db;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers;
|
||||
|
||||
use PhpMyAdmin\BrowseForeigners;
|
||||
use PhpMyAdmin\Relation;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
|
||||
/**
|
||||
* Display selection for relational field values
|
||||
*/
|
||||
class BrowseForeignersController extends AbstractController
|
||||
{
|
||||
/** @var BrowseForeigners */
|
||||
private $browseForeigners;
|
||||
|
||||
/** @var Relation */
|
||||
private $relation;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param BrowseForeigners $browseForeigners
|
||||
* @param Relation $relation
|
||||
*/
|
||||
public function __construct($response, Template $template, $browseForeigners, $relation)
|
||||
{
|
||||
parent::__construct($response, $template);
|
||||
$this->browseForeigners = $browseForeigners;
|
||||
$this->relation = $relation;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
$params = [
|
||||
'db' => $_POST['db'] ?? null,
|
||||
'table' => $_POST['table'] ?? null,
|
||||
'field' => $_POST['field'] ?? null,
|
||||
'fieldkey' => $_POST['fieldkey'] ?? null,
|
||||
'data' => $_POST['data'] ?? null,
|
||||
'foreign_showAll' => $_POST['foreign_showAll'] ?? null,
|
||||
'foreign_filter' => $_POST['foreign_filter'] ?? null,
|
||||
];
|
||||
|
||||
if (! isset($params['db'], $params['table'], $params['field'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response->getFooter()->setMinimal();
|
||||
$header = $this->response->getHeader();
|
||||
$header->disableMenuAndConsole();
|
||||
$header->setBodyId('body_browse_foreigners');
|
||||
|
||||
$foreigners = $this->relation->getForeigners(
|
||||
$params['db'],
|
||||
$params['table']
|
||||
);
|
||||
$foreignLimit = $this->browseForeigners->getForeignLimit(
|
||||
$params['foreign_showAll']
|
||||
);
|
||||
$foreignData = $this->relation->getForeignData(
|
||||
$foreigners,
|
||||
$params['field'],
|
||||
true,
|
||||
$params['foreign_filter'] ?? '',
|
||||
$foreignLimit ?? null,
|
||||
true
|
||||
);
|
||||
|
||||
$this->response->addHTML($this->browseForeigners->getHtmlForRelationalFieldSelection(
|
||||
$params['db'],
|
||||
$params['table'],
|
||||
$params['field'],
|
||||
$foreignData,
|
||||
$params['fieldkey'] ?? '',
|
||||
$params['data'] ?? ''
|
||||
));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
<?php
|
||||
/**
|
||||
* Simple script to set correct charset for changelog
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers;
|
||||
|
||||
use function array_keys;
|
||||
use function file_get_contents;
|
||||
use function htmlspecialchars;
|
||||
use function is_readable;
|
||||
use function ob_get_clean;
|
||||
use function ob_start;
|
||||
use function preg_replace;
|
||||
use function printf;
|
||||
use function readgzfile;
|
||||
use function substr;
|
||||
|
||||
class ChangeLogController extends AbstractController
|
||||
{
|
||||
public function index(): void
|
||||
{
|
||||
$this->response->disable();
|
||||
$this->response->getHeader()->sendHttpHeaders();
|
||||
|
||||
$filename = CHANGELOG_FILE;
|
||||
|
||||
/**
|
||||
* Read changelog.
|
||||
*/
|
||||
// Check if the file is available, some distributions remove these.
|
||||
if (! @is_readable($filename)) {
|
||||
printf(
|
||||
__(
|
||||
'The %s file is not available on this system, please visit ' .
|
||||
'%s for more information.'
|
||||
),
|
||||
$filename,
|
||||
'<a href="https://www.phpmyadmin.net/">phpmyadmin.net</a>'
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Test if the if is in a compressed format
|
||||
if (substr($filename, -3) === '.gz') {
|
||||
ob_start();
|
||||
readgzfile($filename);
|
||||
$changelog = ob_get_clean();
|
||||
} else {
|
||||
$changelog = file_get_contents($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whole changelog in variable.
|
||||
*/
|
||||
$changelog = htmlspecialchars((string) $changelog);
|
||||
|
||||
$github_url = 'https://github.com/phpmyadmin/phpmyadmin/';
|
||||
$faq_url = 'https://docs.phpmyadmin.net/en/latest/faq.html';
|
||||
|
||||
$replaces = [
|
||||
'@(https?://[./a-zA-Z0-9.-_-]*[/a-zA-Z0-9_])@'
|
||||
=> '<a href="url.php?url=\\1">\\1</a>',
|
||||
|
||||
// mail address
|
||||
'/([0-9]{4}-[0-9]{2}-[0-9]{2}) (.+[^ ]) +<(.*@.*)>/i'
|
||||
=> '\\1 <a href="mailto:\\3">\\2</a>',
|
||||
|
||||
// FAQ entries
|
||||
'/FAQ ([0-9]+)\.([0-9a-z]+)/i'
|
||||
=> '<a href="url.php?url=' . $faq_url . '#faq\\1-\\2">FAQ \\1.\\2</a>',
|
||||
|
||||
// GitHub issues
|
||||
'/issue\s*#?([0-9]{4,5}) /i'
|
||||
=> '<a href="url.php?url=' . $github_url . 'issues/\\1">issue #\\1</a> ',
|
||||
|
||||
// CVE/CAN entries
|
||||
'/((CAN|CVE)-[0-9]+-[0-9]+)/'
|
||||
=> '<a href="url.php?url=https://cve.mitre.org/cgi-bin/cvename.cgi?name=\\1">\\1</a>',
|
||||
|
||||
// PMASAentries
|
||||
'/(PMASA-[0-9]+-[0-9]+)/'
|
||||
=> '<a href="url.php?url=https://www.phpmyadmin.net/security/\\1/">\\1</a>',
|
||||
|
||||
// Highlight releases (with links)
|
||||
'/([0-9]+)\.([0-9]+)\.([0-9]+)\.0 (\([0-9-]+\))/'
|
||||
=> '<a id="\\1_\\2_\\3"></a>'
|
||||
. '<a href="url.php?url=' . $github_url . 'commits/RELEASE_\\1_\\2_\\3">'
|
||||
. '\\1.\\2.\\3.0 \\4</a>',
|
||||
'/([0-9]+)\.([0-9]+)\.([0-9]+)\.([1-9][0-9]*) (\([0-9-]+\))/'
|
||||
=> '<a id="\\1_\\2_\\3_\\4"></a>'
|
||||
. '<a href="url.php?url=' . $github_url . 'commits/RELEASE_\\1_\\2_\\3_\\4">'
|
||||
. '\\1.\\2.\\3.\\4 \\5</a>',
|
||||
|
||||
// Highlight releases (not linkable)
|
||||
'/( ### )(.*)/' => '\\1<b>\\2</b>',
|
||||
|
||||
// Links target and rel
|
||||
'/a href="/' => 'a target="_blank" rel="noopener noreferrer" href="',
|
||||
|
||||
];
|
||||
|
||||
$this->response->header('Content-type: text/html; charset=utf-8');
|
||||
|
||||
echo $this->template->render('changelog', [
|
||||
'changelog' => preg_replace(array_keys($replaces), $replaces, $changelog),
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
/**
|
||||
* Displays status of phpMyAdmin configuration storage
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers;
|
||||
|
||||
use PhpMyAdmin\Relation;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
|
||||
class CheckRelationsController extends AbstractController
|
||||
{
|
||||
/** @var Relation */
|
||||
private $relation;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
*/
|
||||
public function __construct($response, Template $template, Relation $relation)
|
||||
{
|
||||
parent::__construct($response, $template);
|
||||
$this->relation = $relation;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $db;
|
||||
|
||||
$params = [
|
||||
'create_pmadb' => $_POST['create_pmadb'] ?? null,
|
||||
'fixall_pmadb' => $_POST['fixall_pmadb'] ?? null,
|
||||
'fix_pmadb' => $_POST['fix_pmadb'] ?? null,
|
||||
];
|
||||
|
||||
// If request for creating the pmadb
|
||||
if (isset($params['create_pmadb']) && $this->relation->createPmaDatabase()) {
|
||||
$this->relation->fixPmaTables('phpmyadmin');
|
||||
}
|
||||
|
||||
// If request for creating all PMA tables.
|
||||
if (isset($params['fixall_pmadb'])) {
|
||||
$this->relation->fixPmaTables($db);
|
||||
}
|
||||
|
||||
$cfgRelation = $this->relation->getRelationsParam();
|
||||
// If request for creating missing PMA tables.
|
||||
if (isset($params['fix_pmadb'])) {
|
||||
$this->relation->fixPmaTables($cfgRelation['db']);
|
||||
}
|
||||
|
||||
$this->response->addHTML($this->relation->getRelationsParamDiagnostic($cfgRelation));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers;
|
||||
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
|
||||
final class ColumnController extends AbstractController
|
||||
{
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template);
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function all(): void
|
||||
{
|
||||
if (! isset($_POST['db'], $_POST['table'])) {
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON(['message' => Message::error()]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response->addJSON(['columns' => $this->dbi->getColumnNames($_POST['db'], $_POST['table'])]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers;
|
||||
|
||||
use PhpMyAdmin\Config;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use function json_decode;
|
||||
|
||||
final class ConfigController extends AbstractController
|
||||
{
|
||||
/** @var Config */
|
||||
private $config;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
*/
|
||||
public function __construct($response, Template $template, Config $config)
|
||||
{
|
||||
parent::__construct($response, $template);
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
public function get(): void
|
||||
{
|
||||
if (! isset($_POST['key'])) {
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON(['message' => Message::error()]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response->addJSON(['value' => $this->config->get($_POST['key'])]);
|
||||
}
|
||||
|
||||
public function set(): void
|
||||
{
|
||||
if (! isset($_POST['key'], $_POST['value'])) {
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON(['message' => Message::error()]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$result = $this->config->setUserValue(null, $_POST['key'], json_decode($_POST['value']));
|
||||
|
||||
if ($result === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON(['message' => $result]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Database;
|
||||
|
||||
use PhpMyAdmin\Controllers\AbstractController as Controller;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
|
||||
abstract class AbstractController extends Controller
|
||||
{
|
||||
/** @var string */
|
||||
protected $db;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name
|
||||
*/
|
||||
public function __construct($response, Template $template, $db)
|
||||
{
|
||||
parent::__construct($response, $template);
|
||||
$this->db = $db;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,302 @@
|
|||
<?php
|
||||
/**
|
||||
* Central Columns view/edit
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Database;
|
||||
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\Database\CentralColumns;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use function is_bool;
|
||||
use function parse_str;
|
||||
use function sprintf;
|
||||
|
||||
class CentralColumnsController extends AbstractController
|
||||
{
|
||||
/** @var CentralColumns */
|
||||
private $centralColumns;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name
|
||||
* @param CentralColumns $centralColumns
|
||||
*/
|
||||
public function __construct($response, Template $template, $db, $centralColumns)
|
||||
{
|
||||
parent::__construct($response, $template, $db);
|
||||
$this->centralColumns = $centralColumns;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $cfg, $db, $message, $pos, $num_cols;
|
||||
|
||||
if (isset($_POST['edit_save'])) {
|
||||
echo $this->editSave([
|
||||
'col_name' => $_POST['col_name'] ?? null,
|
||||
'orig_col_name' => $_POST['orig_col_name'] ?? null,
|
||||
'col_default' => $_POST['col_default'] ?? null,
|
||||
'col_default_sel' => $_POST['col_default_sel'] ?? null,
|
||||
'col_extra' => $_POST['col_extra'] ?? null,
|
||||
'col_isNull' => $_POST['col_isNull'] ?? null,
|
||||
'col_length' => $_POST['col_length'] ?? null,
|
||||
'col_attribute' => $_POST['col_attribute'] ?? null,
|
||||
'col_type' => $_POST['col_type'] ?? null,
|
||||
'collation' => $_POST['collation'] ?? null,
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($_POST['add_new_column'])) {
|
||||
$tmp_msg = $this->addNewColumn([
|
||||
'col_name' => $_POST['col_name'] ?? null,
|
||||
'col_default' => $_POST['col_default'] ?? null,
|
||||
'col_default_sel' => $_POST['col_default_sel'] ?? null,
|
||||
'col_extra' => $_POST['col_extra'] ?? null,
|
||||
'col_isNull' => $_POST['col_isNull'] ?? null,
|
||||
'col_length' => $_POST['col_length'] ?? null,
|
||||
'col_attribute' => $_POST['col_attribute'] ?? null,
|
||||
'col_type' => $_POST['col_type'] ?? null,
|
||||
'collation' => $_POST['collation'] ?? null,
|
||||
]);
|
||||
}
|
||||
if (isset($_POST['getColumnList'])) {
|
||||
$this->response->addJSON('message', $this->getColumnList([
|
||||
'cur_table' => $_POST['cur_table'] ?? null,
|
||||
]));
|
||||
|
||||
return;
|
||||
}
|
||||
if (isset($_POST['add_column'])) {
|
||||
$tmp_msg = $this->addColumn([
|
||||
'table-select' => $_POST['table-select'] ?? null,
|
||||
'column-select' => $_POST['column-select'] ?? null,
|
||||
]);
|
||||
}
|
||||
|
||||
$this->addScriptFiles([
|
||||
'vendor/jquery/jquery.uitablefilter.js',
|
||||
'vendor/jquery/jquery.tablesorter.js',
|
||||
'database/central_columns.js',
|
||||
]);
|
||||
|
||||
if (isset($_POST['edit_central_columns_page'])) {
|
||||
$this->editPage([
|
||||
'selected_fld' => $_POST['selected_fld'] ?? null,
|
||||
'db' => $_POST['db'] ?? null,
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
if (isset($_POST['multi_edit_central_column_save'])) {
|
||||
$message = $this->updateMultipleColumn([
|
||||
'db' => $_POST['db'] ?? null,
|
||||
'orig_col_name' => $_POST['orig_col_name'] ?? null,
|
||||
'field_name' => $_POST['field_name'] ?? null,
|
||||
'field_default_type' => $_POST['field_default_type'] ?? null,
|
||||
'field_default_value' => $_POST['field_default_value'] ?? null,
|
||||
'field_length' => $_POST['field_length'] ?? null,
|
||||
'field_attribute' => $_POST['field_attribute'] ?? null,
|
||||
'field_type' => $_POST['field_type'] ?? null,
|
||||
'field_collation' => $_POST['field_collation'] ?? null,
|
||||
'field_null' => $_POST['field_null'] ?? null,
|
||||
'col_extra' => $_POST['col_extra'] ?? null,
|
||||
]);
|
||||
if (! is_bool($message)) {
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON('message', $message);
|
||||
}
|
||||
}
|
||||
if (isset($_POST['delete_save'])) {
|
||||
$tmp_msg = $this->deleteSave([
|
||||
'db' => $_POST['db'] ?? null,
|
||||
'col_name' => $_POST['col_name'] ?? null,
|
||||
]);
|
||||
}
|
||||
|
||||
$this->main([
|
||||
'pos' => $_POST['pos'] ?? null,
|
||||
'total_rows' => $_POST['total_rows'] ?? null,
|
||||
]);
|
||||
|
||||
$pos = 0;
|
||||
if (Core::isValid($_POST['pos'], 'integer')) {
|
||||
$pos = (int) $_POST['pos'];
|
||||
}
|
||||
$num_cols = $this->centralColumns->getColumnsCount(
|
||||
$db,
|
||||
$pos,
|
||||
(int) $cfg['MaxRows']
|
||||
);
|
||||
$message = Message::success(
|
||||
sprintf(__('Showing rows %1$s - %2$s.'), $pos + 1, $pos + $num_cols)
|
||||
);
|
||||
if (! isset($tmp_msg) || $tmp_msg === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
$message = $tmp_msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $params Request parameters
|
||||
*/
|
||||
public function main(array $params): void
|
||||
{
|
||||
global $text_dir, $PMA_Theme;
|
||||
|
||||
if (! empty($params['total_rows'])
|
||||
&& Core::isValid($params['total_rows'], 'integer')
|
||||
) {
|
||||
$totalRows = (int) $params['total_rows'];
|
||||
} else {
|
||||
$totalRows = $this->centralColumns->getCount($this->db);
|
||||
}
|
||||
|
||||
$pos = 0;
|
||||
if (Core::isValid($params['pos'], 'integer')) {
|
||||
$pos = (int) $params['pos'];
|
||||
}
|
||||
|
||||
$variables = $this->centralColumns->getTemplateVariablesForMain(
|
||||
$this->db,
|
||||
$totalRows,
|
||||
$pos,
|
||||
$PMA_Theme->getImgPath(),
|
||||
$text_dir
|
||||
);
|
||||
|
||||
$this->render('database/central_columns/main', $variables);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $params Request parameters
|
||||
*
|
||||
* @return array JSON
|
||||
*/
|
||||
public function getColumnList(array $params): array
|
||||
{
|
||||
return $this->centralColumns->getListRaw(
|
||||
$this->db,
|
||||
$params['cur_table'] ?? ''
|
||||
);
|
||||
}
|
||||
|
||||
public function populateColumns(): void
|
||||
{
|
||||
$columns = $this->centralColumns->getColumnsNotInCentralList($this->db, $_POST['selectedTable']);
|
||||
$this->render('database/central_columns/populate_columns', ['columns' => $columns]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $params Request parameters
|
||||
*
|
||||
* @return true|Message
|
||||
*/
|
||||
public function editSave(array $params)
|
||||
{
|
||||
$columnDefault = $params['col_default'];
|
||||
if ($columnDefault === 'NONE' && $params['col_default_sel'] !== 'USER_DEFINED') {
|
||||
$columnDefault = '';
|
||||
}
|
||||
|
||||
return $this->centralColumns->updateOneColumn(
|
||||
$this->db,
|
||||
$params['orig_col_name'],
|
||||
$params['col_name'],
|
||||
$params['col_type'],
|
||||
$params['col_attribute'],
|
||||
$params['col_length'],
|
||||
isset($params['col_isNull']) ? 1 : 0,
|
||||
$params['collation'],
|
||||
$params['col_extra'] ?? '',
|
||||
$columnDefault
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $params Request parameters
|
||||
*
|
||||
* @return true|Message
|
||||
*/
|
||||
public function addNewColumn(array $params)
|
||||
{
|
||||
$columnDefault = $params['col_default'];
|
||||
if ($columnDefault === 'NONE' && $params['col_default_sel'] !== 'USER_DEFINED') {
|
||||
$columnDefault = '';
|
||||
}
|
||||
|
||||
return $this->centralColumns->updateOneColumn(
|
||||
$this->db,
|
||||
'',
|
||||
$params['col_name'],
|
||||
$params['col_type'],
|
||||
$params['col_attribute'],
|
||||
$params['col_length'],
|
||||
isset($params['col_isNull']) ? 1 : 0,
|
||||
$params['collation'],
|
||||
$params['col_extra'] ?? '',
|
||||
$columnDefault
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $params Request parameters
|
||||
*
|
||||
* @return true|Message
|
||||
*/
|
||||
public function addColumn(array $params)
|
||||
{
|
||||
return $this->centralColumns->syncUniqueColumns(
|
||||
[$params['column-select']],
|
||||
false,
|
||||
$params['table-select']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $params Request parameters
|
||||
*/
|
||||
public function editPage(array $params): void
|
||||
{
|
||||
$rows = $this->centralColumns->getHtmlForEditingPage(
|
||||
$params['selected_fld'],
|
||||
$params['db']
|
||||
);
|
||||
|
||||
$this->render('database/central_columns/edit', ['rows' => $rows]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $params Request parameters
|
||||
*
|
||||
* @return true|Message
|
||||
*/
|
||||
public function updateMultipleColumn(array $params)
|
||||
{
|
||||
return $this->centralColumns->updateMultipleColumn($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $params Request parameters
|
||||
*
|
||||
* @return true|Message
|
||||
*/
|
||||
public function deleteSave(array $params)
|
||||
{
|
||||
$name = [];
|
||||
parse_str($params['col_name'], $name);
|
||||
|
||||
return $this->centralColumns->deleteColumnsFromList(
|
||||
$params['db'],
|
||||
$name['selected_fld'],
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Database;
|
||||
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Index;
|
||||
use PhpMyAdmin\Relation;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Transformations;
|
||||
use PhpMyAdmin\Util;
|
||||
use function is_array;
|
||||
use function str_replace;
|
||||
|
||||
class DataDictionaryController extends AbstractController
|
||||
{
|
||||
/** @var Relation */
|
||||
private $relation;
|
||||
|
||||
/** @var Transformations */
|
||||
private $transformations;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name
|
||||
* @param Relation $relation
|
||||
* @param Transformations $transformations
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $db, $relation, $transformations, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template, $db);
|
||||
$this->relation = $relation;
|
||||
$this->transformations = $transformations;
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
Util::checkParameters(['db'], true);
|
||||
|
||||
$header = $this->response->getHeader();
|
||||
$header->enablePrintView();
|
||||
|
||||
$cfgRelation = $this->relation->getRelationsParam();
|
||||
|
||||
$comment = $this->relation->getDbComment($this->db);
|
||||
|
||||
$this->dbi->selectDb($this->db);
|
||||
$tablesNames = $this->dbi->getTables($this->db);
|
||||
|
||||
$tables = [];
|
||||
foreach ($tablesNames as $tableName) {
|
||||
$showComment = (string) $this->dbi->getTable(
|
||||
$this->db,
|
||||
$tableName
|
||||
)->getStatusInfo('TABLE_COMMENT');
|
||||
|
||||
[, $primaryKeys] = Util::processIndexData(
|
||||
$this->dbi->getTableIndexes($this->db, $tableName)
|
||||
);
|
||||
|
||||
[$foreigners, $hasRelation] = $this->relation->getRelationsAndStatus(
|
||||
! empty($cfgRelation['relation']),
|
||||
$this->db,
|
||||
$tableName
|
||||
);
|
||||
|
||||
$columnsComments = $this->relation->getComments($this->db, $tableName);
|
||||
|
||||
$columns = $this->dbi->getColumns($this->db, $tableName);
|
||||
$rows = [];
|
||||
foreach ($columns as $row) {
|
||||
$extractedColumnSpec = Util::extractColumnSpec($row['Type']);
|
||||
|
||||
$relation = '';
|
||||
if ($hasRelation) {
|
||||
$foreigner = $this->relation->searchColumnInForeigners(
|
||||
$foreigners,
|
||||
$row['Field']
|
||||
);
|
||||
if (is_array($foreigner) && isset($foreigner['foreign_table'], $foreigner['foreign_field'])) {
|
||||
$relation = $foreigner['foreign_table'];
|
||||
$relation .= ' -> ';
|
||||
$relation .= $foreigner['foreign_field'];
|
||||
}
|
||||
}
|
||||
|
||||
$mime = '';
|
||||
if ($cfgRelation['mimework']) {
|
||||
$mimeMap = $this->transformations->getMime(
|
||||
$this->db,
|
||||
$tableName,
|
||||
true
|
||||
);
|
||||
if (is_array($mimeMap) && isset($mimeMap[$row['Field']]['mimetype'])) {
|
||||
$mime = str_replace(
|
||||
'_',
|
||||
'/',
|
||||
$mimeMap[$row['Field']]['mimetype']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$rows[$row['Field']] = [
|
||||
'name' => $row['Field'],
|
||||
'has_primary_key' => isset($primaryKeys[$row['Field']]),
|
||||
'type' => $extractedColumnSpec['type'],
|
||||
'print_type' => $extractedColumnSpec['print_type'],
|
||||
'is_nullable' => $row['Null'] !== '' && $row['Null'] !== 'NO',
|
||||
'default' => $row['Default'] ?? null,
|
||||
'comment' => $columnsComments[$row['Field']] ?? '',
|
||||
'mime' => $mime,
|
||||
'relation' => $relation,
|
||||
];
|
||||
}
|
||||
|
||||
$tables[$tableName] = [
|
||||
'name' => $tableName,
|
||||
'comment' => $showComment,
|
||||
'has_relation' => $hasRelation,
|
||||
'has_mime' => $cfgRelation['mimework'],
|
||||
'columns' => $rows,
|
||||
'indexes' => Index::getFromTable($tableName, $this->db),
|
||||
];
|
||||
}
|
||||
|
||||
$this->render('database/data_dictionary/index', [
|
||||
'database' => $this->db,
|
||||
'comment' => $comment,
|
||||
'tables' => $tables,
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,259 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Database;
|
||||
|
||||
use PhpMyAdmin\Database\Designer;
|
||||
use PhpMyAdmin\Database\Designer\Common as DesignerCommon;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use function htmlspecialchars;
|
||||
use function in_array;
|
||||
use function sprintf;
|
||||
|
||||
class DesignerController extends AbstractController
|
||||
{
|
||||
/** @var Designer */
|
||||
private $databaseDesigner;
|
||||
|
||||
/** @var DesignerCommon */
|
||||
private $designerCommon;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name
|
||||
*/
|
||||
public function __construct(
|
||||
$response,
|
||||
Template $template,
|
||||
$db,
|
||||
Designer $databaseDesigner,
|
||||
DesignerCommon $designerCommon
|
||||
) {
|
||||
parent::__construct($response, $template, $db);
|
||||
$this->databaseDesigner = $databaseDesigner;
|
||||
$this->designerCommon = $designerCommon;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $db, $script_display_field, $tab_column, $tables_all_keys, $tables_pk_or_unique_keys;
|
||||
global $success, $page, $message, $display_page, $selected_page, $tab_pos, $fullTableNames, $script_tables;
|
||||
global $script_contr, $params, $tables, $num_tables, $total_num_tables, $sub_part;
|
||||
global $tooltip_truename, $tooltip_aliasname, $pos, $classes_side_menu, $cfg, $err_url;
|
||||
|
||||
if (isset($_POST['dialog'])) {
|
||||
if ($_POST['dialog'] === 'edit') {
|
||||
$html = $this->databaseDesigner->getHtmlForEditOrDeletePages($_POST['db'], 'editPage');
|
||||
} elseif ($_POST['dialog'] === 'delete') {
|
||||
$html = $this->databaseDesigner->getHtmlForEditOrDeletePages($_POST['db'], 'deletePage');
|
||||
} elseif ($_POST['dialog'] === 'save_as') {
|
||||
$html = $this->databaseDesigner->getHtmlForPageSaveAs($_POST['db']);
|
||||
} elseif ($_POST['dialog'] === 'export') {
|
||||
$html = $this->databaseDesigner->getHtmlForSchemaExport(
|
||||
$_POST['db'],
|
||||
$_POST['selected_page']
|
||||
);
|
||||
} elseif ($_POST['dialog'] === 'add_table') {
|
||||
// Pass the db and table to the getTablesInfo so we only have the table we asked for
|
||||
$script_display_field = $this->designerCommon->getTablesInfo($_POST['db'], $_POST['table']);
|
||||
$tab_column = $this->designerCommon->getColumnsInfo($script_display_field);
|
||||
$tables_all_keys = $this->designerCommon->getAllKeys($script_display_field);
|
||||
$tables_pk_or_unique_keys = $this->designerCommon->getPkOrUniqueKeys($script_display_field);
|
||||
|
||||
$html = $this->databaseDesigner->getDatabaseTables(
|
||||
$_POST['db'],
|
||||
$script_display_field,
|
||||
[],
|
||||
-1,
|
||||
$tab_column,
|
||||
$tables_all_keys,
|
||||
$tables_pk_or_unique_keys
|
||||
);
|
||||
}
|
||||
|
||||
if (! empty($html)) {
|
||||
$this->response->addHTML($html);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($_POST['operation'])) {
|
||||
if ($_POST['operation'] === 'deletePage') {
|
||||
$success = $this->designerCommon->deletePage($_POST['selected_page']);
|
||||
$this->response->setRequestStatus($success);
|
||||
} elseif ($_POST['operation'] === 'savePage') {
|
||||
if ($_POST['save_page'] === 'same') {
|
||||
$page = $_POST['selected_page'];
|
||||
} elseif ($this->designerCommon->getPageExists($_POST['selected_value'])) {
|
||||
$this->response->addJSON(
|
||||
'message',
|
||||
/* l10n: The user tries to save a page with an existing name in Designer */
|
||||
__(
|
||||
sprintf(
|
||||
'There already exists a page named "%s" please rename it to something else.',
|
||||
htmlspecialchars($_POST['selected_value'])
|
||||
)
|
||||
)
|
||||
);
|
||||
$this->response->setRequestStatus(false);
|
||||
|
||||
return;
|
||||
} else {
|
||||
$page = $this->designerCommon->createNewPage($_POST['selected_value'], $_POST['db']);
|
||||
$this->response->addJSON('id', $page);
|
||||
}
|
||||
$success = $this->designerCommon->saveTablePositions($page);
|
||||
$this->response->setRequestStatus($success);
|
||||
} elseif ($_POST['operation'] === 'setDisplayField') {
|
||||
[
|
||||
$success,
|
||||
$message,
|
||||
] = $this->designerCommon->saveDisplayField(
|
||||
$_POST['db'],
|
||||
$_POST['table'],
|
||||
$_POST['field']
|
||||
);
|
||||
$this->response->setRequestStatus($success);
|
||||
$this->response->addJSON('message', $message);
|
||||
} elseif ($_POST['operation'] === 'addNewRelation') {
|
||||
[$success, $message] = $this->designerCommon->addNewRelation(
|
||||
$_POST['db'],
|
||||
$_POST['T1'],
|
||||
$_POST['F1'],
|
||||
$_POST['T2'],
|
||||
$_POST['F2'],
|
||||
$_POST['on_delete'],
|
||||
$_POST['on_update'],
|
||||
$_POST['DB1'],
|
||||
$_POST['DB2']
|
||||
);
|
||||
$this->response->setRequestStatus($success);
|
||||
$this->response->addJSON('message', $message);
|
||||
} elseif ($_POST['operation'] === 'removeRelation') {
|
||||
[$success, $message] = $this->designerCommon->removeRelation(
|
||||
$_POST['T1'],
|
||||
$_POST['F1'],
|
||||
$_POST['T2'],
|
||||
$_POST['F2']
|
||||
);
|
||||
$this->response->setRequestStatus($success);
|
||||
$this->response->addJSON('message', $message);
|
||||
} elseif ($_POST['operation'] === 'save_setting_value') {
|
||||
$success = $this->designerCommon->saveSetting($_POST['index'], $_POST['value']);
|
||||
$this->response->setRequestStatus($success);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Util::checkParameters(['db']);
|
||||
|
||||
$err_url = Util::getScriptNameForOption($cfg['DefaultTabDatabase'], 'database');
|
||||
$err_url .= Url::getCommon(['db' => $db], '&');
|
||||
|
||||
if (! $this->hasDatabase()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$script_display_field = $this->designerCommon->getTablesInfo();
|
||||
|
||||
$display_page = -1;
|
||||
$selected_page = null;
|
||||
|
||||
$visualBuilderMode = isset($_GET['query']);
|
||||
|
||||
if ($visualBuilderMode) {
|
||||
$display_page = $this->designerCommon->getDefaultPage($_GET['db']);
|
||||
} elseif (! empty($_GET['page'])) {
|
||||
$display_page = $_GET['page'];
|
||||
} else {
|
||||
$display_page = $this->designerCommon->getLoadingPage($_GET['db']);
|
||||
}
|
||||
if ($display_page != -1) {
|
||||
$selected_page = $this->designerCommon->getPageName($display_page);
|
||||
}
|
||||
$tab_pos = $this->designerCommon->getTablePositions($display_page);
|
||||
|
||||
$fullTableNames = [];
|
||||
|
||||
foreach ($script_display_field as $designerTable) {
|
||||
$fullTableNames[] = $designerTable->getDbTableString();
|
||||
}
|
||||
|
||||
foreach ($tab_pos as $position) {
|
||||
if (in_array($position['dbName'] . '.' . $position['tableName'], $fullTableNames)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$designerTables = $this->designerCommon->getTablesInfo($position['dbName'], $position['tableName']);
|
||||
foreach ($designerTables as $designerTable) {
|
||||
$script_display_field[] = $designerTable;
|
||||
}
|
||||
}
|
||||
|
||||
$tab_column = $this->designerCommon->getColumnsInfo($script_display_field);
|
||||
$script_tables = $this->designerCommon->getScriptTabs($script_display_field);
|
||||
$tables_pk_or_unique_keys = $this->designerCommon->getPkOrUniqueKeys($script_display_field);
|
||||
$tables_all_keys = $this->designerCommon->getAllKeys($script_display_field);
|
||||
$classes_side_menu = $this->databaseDesigner->returnClassNamesFromMenuButtons();
|
||||
|
||||
$script_contr = $this->designerCommon->getScriptContr($script_display_field);
|
||||
|
||||
$params = ['lang' => $GLOBALS['lang']];
|
||||
if (isset($_GET['db'])) {
|
||||
$params['db'] = $_GET['db'];
|
||||
}
|
||||
|
||||
$this->response->getFooter()->setMinimal();
|
||||
$header = $this->response->getHeader();
|
||||
$header->setBodyId('designer_body');
|
||||
|
||||
$this->addScriptFiles([
|
||||
'vendor/jquery/jquery.fullscreen.js',
|
||||
'designer/database.js',
|
||||
'designer/objects.js',
|
||||
'designer/page.js',
|
||||
'designer/history.js',
|
||||
'designer/move.js',
|
||||
'designer/init.js',
|
||||
]);
|
||||
|
||||
[
|
||||
$tables,
|
||||
$num_tables,
|
||||
$total_num_tables,
|
||||
$sub_part,,,
|
||||
$tooltip_truename,
|
||||
$tooltip_aliasname,
|
||||
$pos,
|
||||
] = Util::getDbInfo($db, $sub_part ?? '');
|
||||
|
||||
// Embed some data into HTML, later it will be read
|
||||
// by designer/init.js and converted to JS variables.
|
||||
$this->response->addHTML(
|
||||
$this->databaseDesigner->getHtmlForMain(
|
||||
$db,
|
||||
$_GET['db'],
|
||||
$script_display_field,
|
||||
$script_tables,
|
||||
$script_contr,
|
||||
$script_display_field,
|
||||
$display_page,
|
||||
$visualBuilderMode,
|
||||
$selected_page,
|
||||
$classes_side_menu,
|
||||
$tab_pos,
|
||||
$tab_column,
|
||||
$tables_all_keys,
|
||||
$tables_pk_or_unique_keys
|
||||
)
|
||||
);
|
||||
|
||||
$this->response->addHTML('<div id="PMA_disable_floating_menubar"></div>');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Database;
|
||||
|
||||
use PhpMyAdmin\Database\Events;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use function strlen;
|
||||
|
||||
final class EventsController extends AbstractController
|
||||
{
|
||||
/** @var Events */
|
||||
private $events;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name.
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $db, Events $events, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template, $db);
|
||||
$this->events = $events;
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $db, $tables, $num_tables, $total_num_tables, $sub_part, $errors, $text_dir, $PMA_Theme;
|
||||
global $tooltip_truename, $tooltip_aliasname, $pos, $cfg, $err_url;
|
||||
|
||||
if (! $this->response->isAjax()) {
|
||||
Util::checkParameters(['db']);
|
||||
|
||||
$err_url = Util::getScriptNameForOption($cfg['DefaultTabDatabase'], 'database');
|
||||
$err_url .= Url::getCommon(['db' => $db], '&');
|
||||
|
||||
if (! $this->hasDatabase()) {
|
||||
return;
|
||||
}
|
||||
|
||||
[
|
||||
$tables,
|
||||
$num_tables,
|
||||
$total_num_tables,
|
||||
$sub_part,,,
|
||||
$tooltip_truename,
|
||||
$tooltip_aliasname,
|
||||
$pos,
|
||||
] = Util::getDbInfo($db, $sub_part ?? '');
|
||||
} elseif (strlen($db) > 0) {
|
||||
$this->dbi->selectDb($db);
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep a list of errors that occurred while
|
||||
* processing an 'Add' or 'Edit' operation.
|
||||
*/
|
||||
$errors = [];
|
||||
|
||||
$this->events->handleEditor();
|
||||
$this->events->export();
|
||||
|
||||
$items = $this->dbi->getEvents($db);
|
||||
|
||||
$this->render('database/events/index', [
|
||||
'db' => $db,
|
||||
'items' => $items,
|
||||
'select_all_arrow_src' => $PMA_Theme->getImgPath() . 'arrow_' . $text_dir . '.png',
|
||||
'has_privilege' => Util::currentUserHasPrivilege('EVENT', $db),
|
||||
'scheduler_state' => $this->events->getEventSchedulerStatus(),
|
||||
'text_dir' => $text_dir,
|
||||
'theme_image_path' => $PMA_Theme->getImgPath(),
|
||||
'is_ajax' => $this->response->isAjax() && empty($_REQUEST['ajax_page_request']),
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,184 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Database;
|
||||
|
||||
use PhpMyAdmin\Config\PageSettings;
|
||||
use PhpMyAdmin\Export;
|
||||
use PhpMyAdmin\Export\Options;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\Plugins;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use function array_merge;
|
||||
use function is_array;
|
||||
|
||||
final class ExportController extends AbstractController
|
||||
{
|
||||
/** @var Export */
|
||||
private $export;
|
||||
|
||||
/** @var Options */
|
||||
private $exportOptions;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name.
|
||||
*/
|
||||
public function __construct($response, Template $template, $db, Export $export, Options $exportOptions)
|
||||
{
|
||||
parent::__construct($response, $template, $db);
|
||||
$this->export = $export;
|
||||
$this->exportOptions = $exportOptions;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $db, $table, $sub_part, $url_params, $sql_query;
|
||||
global $tables, $num_tables, $total_num_tables, $tooltip_truename;
|
||||
global $tooltip_aliasname, $pos, $table_select, $unlim_num_rows, $cfg, $err_url;
|
||||
|
||||
$pageSettings = new PageSettings('Export');
|
||||
$pageSettingsErrorHtml = $pageSettings->getErrorHTML();
|
||||
$pageSettingsHtml = $pageSettings->getHTML();
|
||||
|
||||
$this->addScriptFiles(['export.js']);
|
||||
|
||||
// $sub_part is used in Util::getDbInfo() to see if we are coming from
|
||||
// /database/export, in which case we don't obey $cfg['MaxTableList']
|
||||
$sub_part = '_export';
|
||||
|
||||
Util::checkParameters(['db']);
|
||||
|
||||
$err_url = Util::getScriptNameForOption($cfg['DefaultTabDatabase'], 'database');
|
||||
$err_url .= Url::getCommon(['db' => $db], '&');
|
||||
|
||||
if (! $this->hasDatabase()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$url_params['goto'] = Url::getFromRoute('/database/export');
|
||||
|
||||
[
|
||||
$tables,
|
||||
$num_tables,
|
||||
$total_num_tables,
|
||||
$sub_part,,,
|
||||
$tooltip_truename,
|
||||
$tooltip_aliasname,
|
||||
$pos,
|
||||
] = Util::getDbInfo($db, $sub_part ?? '');
|
||||
|
||||
// exit if no tables in db found
|
||||
if ($num_tables < 1) {
|
||||
$this->response->addHTML(
|
||||
Message::error(__('No tables found in database.'))->getDisplay()
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (! empty($_POST['selected_tbl']) && empty($table_select)) {
|
||||
$table_select = $_POST['selected_tbl'];
|
||||
}
|
||||
|
||||
$tablesForMultiValues = [];
|
||||
|
||||
foreach ($tables as $each_table) {
|
||||
if (isset($_POST['table_select']) && is_array($_POST['table_select'])) {
|
||||
$is_checked = $this->export->getCheckedClause(
|
||||
$each_table['Name'],
|
||||
$_POST['table_select']
|
||||
);
|
||||
} elseif (isset($table_select)) {
|
||||
$is_checked = $this->export->getCheckedClause(
|
||||
$each_table['Name'],
|
||||
$table_select
|
||||
);
|
||||
} else {
|
||||
$is_checked = true;
|
||||
}
|
||||
if (isset($_POST['table_structure']) && is_array($_POST['table_structure'])) {
|
||||
$structure_checked = $this->export->getCheckedClause(
|
||||
$each_table['Name'],
|
||||
$_POST['table_structure']
|
||||
);
|
||||
} else {
|
||||
$structure_checked = $is_checked;
|
||||
}
|
||||
if (isset($_POST['table_data']) && is_array($_POST['table_data'])) {
|
||||
$data_checked = $this->export->getCheckedClause(
|
||||
$each_table['Name'],
|
||||
$_POST['table_data']
|
||||
);
|
||||
} else {
|
||||
$data_checked = $is_checked;
|
||||
}
|
||||
|
||||
$tablesForMultiValues[] = [
|
||||
'name' => $each_table['Name'],
|
||||
'is_checked_select' => $is_checked,
|
||||
'is_checked_structure' => $structure_checked,
|
||||
'is_checked_data' => $data_checked,
|
||||
];
|
||||
}
|
||||
|
||||
if (! isset($sql_query)) {
|
||||
$sql_query = '';
|
||||
}
|
||||
if (! isset($unlim_num_rows)) {
|
||||
$unlim_num_rows = 0;
|
||||
}
|
||||
|
||||
$isReturnBackFromRawExport = isset($_POST['export_type']) && $_POST['export_type'] === 'raw';
|
||||
if (isset($_POST['raw_query']) || $isReturnBackFromRawExport) {
|
||||
$export_type = 'raw';
|
||||
} else {
|
||||
$export_type = 'database';
|
||||
}
|
||||
|
||||
$GLOBALS['single_table'] = $_POST['single_table'] ?? $_GET['single_table'] ?? $GLOBALS['single_table'] ?? null;
|
||||
|
||||
$exportList = Plugins::getExport($export_type, isset($GLOBALS['single_table']));
|
||||
|
||||
if (empty($exportList)) {
|
||||
$this->response->addHTML(Message::error(
|
||||
__('Could not load export plugins, please check your installation!')
|
||||
)->getDisplay());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$options = $this->exportOptions->getOptions(
|
||||
$export_type,
|
||||
$db,
|
||||
$table,
|
||||
$sql_query,
|
||||
$num_tables,
|
||||
$unlim_num_rows,
|
||||
$exportList
|
||||
);
|
||||
|
||||
$this->render('database/export/index', array_merge($options, [
|
||||
'page_settings_error_html' => $pageSettingsErrorHtml,
|
||||
'page_settings_html' => $pageSettingsHtml,
|
||||
'structure_or_data_forced' => $_POST['structure_or_data_forced'] ?? 0,
|
||||
'tables' => $tablesForMultiValues,
|
||||
]));
|
||||
}
|
||||
|
||||
public function tables(): void
|
||||
{
|
||||
if (empty($_POST['selected_tbl'])) {
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON('message', __('No table selected.'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->index();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Database;
|
||||
|
||||
use PhpMyAdmin\Charsets;
|
||||
use PhpMyAdmin\Charsets\Charset;
|
||||
use PhpMyAdmin\Config\PageSettings;
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Encoding;
|
||||
use PhpMyAdmin\Import;
|
||||
use PhpMyAdmin\Import\Ajax;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\Plugins;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use function intval;
|
||||
|
||||
final class ImportController extends AbstractController
|
||||
{
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name.
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $db, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template, $db);
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $db, $max_upload_size, $table, $tables, $num_tables, $total_num_tables, $cfg;
|
||||
global $tooltip_truename, $tooltip_aliasname, $pos, $sub_part, $SESSION_KEY, $PMA_Theme, $err_url;
|
||||
|
||||
$pageSettings = new PageSettings('Import');
|
||||
$pageSettingsErrorHtml = $pageSettings->getErrorHTML();
|
||||
$pageSettingsHtml = $pageSettings->getHTML();
|
||||
|
||||
$this->addScriptFiles(['import.js']);
|
||||
|
||||
Util::checkParameters(['db']);
|
||||
|
||||
$err_url = Util::getScriptNameForOption($cfg['DefaultTabDatabase'], 'database');
|
||||
$err_url .= Url::getCommon(['db' => $db], '&');
|
||||
|
||||
if (! $this->hasDatabase()) {
|
||||
return;
|
||||
}
|
||||
|
||||
[
|
||||
$tables,
|
||||
$num_tables,
|
||||
$total_num_tables,
|
||||
$sub_part,,,
|
||||
$tooltip_truename,
|
||||
$tooltip_aliasname,
|
||||
$pos,
|
||||
] = Util::getDbInfo($db, $sub_part ?? '');
|
||||
|
||||
[$SESSION_KEY, $uploadId] = Ajax::uploadProgressSetup();
|
||||
|
||||
$importList = Plugins::getImport('database');
|
||||
|
||||
if (empty($importList)) {
|
||||
$this->response->addHTML(Message::error(__(
|
||||
'Could not load import plugins, please check your installation!'
|
||||
))->getDisplay());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$offset = null;
|
||||
if (Core::isValid($_REQUEST['offset'], 'numeric')) {
|
||||
$offset = intval($_REQUEST['offset']);
|
||||
}
|
||||
|
||||
$timeoutPassed = $_REQUEST['timeout_passed'] ?? null;
|
||||
$localImportFile = $_REQUEST['local_import_file'] ?? null;
|
||||
$compressions = Import::getCompressions();
|
||||
|
||||
$allCharsets = Charsets::getCharsets($this->dbi, $cfg['Server']['DisableIS']);
|
||||
$charsets = [];
|
||||
/** @var Charset $charset */
|
||||
foreach ($allCharsets as $charset) {
|
||||
$charsets[] = [
|
||||
'name' => $charset->getName(),
|
||||
'description' => $charset->getDescription(),
|
||||
];
|
||||
}
|
||||
|
||||
$idKey = $_SESSION[$SESSION_KEY]['handler']::getIdKey();
|
||||
$hiddenInputs = [
|
||||
$idKey => $uploadId,
|
||||
'import_type' => 'database',
|
||||
'db' => $db,
|
||||
];
|
||||
|
||||
$this->render('database/import/index', [
|
||||
'page_settings_error_html' => $pageSettingsErrorHtml,
|
||||
'page_settings_html' => $pageSettingsHtml,
|
||||
'upload_id' => $uploadId,
|
||||
'handler' => $_SESSION[$SESSION_KEY]['handler'],
|
||||
'theme_image_path' => $PMA_Theme->getImgPath(),
|
||||
'hidden_inputs' => $hiddenInputs,
|
||||
'db' => $db,
|
||||
'table' => $table,
|
||||
'max_upload_size' => $max_upload_size,
|
||||
'import_list' => $importList,
|
||||
'local_import_file' => $localImportFile,
|
||||
'is_upload' => $GLOBALS['is_upload'],
|
||||
'upload_dir' => $cfg['UploadDir'] ?? null,
|
||||
'timeout_passed_global' => $GLOBALS['timeout_passed'] ?? null,
|
||||
'compressions' => $compressions,
|
||||
'is_encoding_supported' => Encoding::isSupported(),
|
||||
'encodings' => Encoding::listEncodings(),
|
||||
'import_charset' => $cfg['Import']['charset'] ?? null,
|
||||
'timeout_passed' => $timeoutPassed,
|
||||
'offset' => $offset,
|
||||
'can_convert_kanji' => Encoding::canConvertKanji(),
|
||||
'charsets' => $charsets,
|
||||
'is_foreign_key_check' => Util::isForeignKeyCheck(),
|
||||
'user_upload_dir' => Util::userDir($cfg['UploadDir'] ?? ''),
|
||||
'local_files' => Import::getLocalFiles($importList),
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Database;
|
||||
|
||||
use PhpMyAdmin\Database\MultiTableQuery;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
|
||||
/**
|
||||
* Handles database multi-table querying
|
||||
*/
|
||||
class MultiTableQueryController extends AbstractController
|
||||
{
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name.
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $db, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template, $db);
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
$this->addScriptFiles([
|
||||
'vendor/jquery/jquery.md5.js',
|
||||
'database/multi_table_query.js',
|
||||
'database/query_generator.js',
|
||||
]);
|
||||
|
||||
$queryInstance = new MultiTableQuery($this->dbi, $this->template, $this->db);
|
||||
|
||||
$this->response->addHTML($queryInstance->getFormHtml());
|
||||
}
|
||||
|
||||
public function displayResults(): void
|
||||
{
|
||||
global $PMA_Theme;
|
||||
|
||||
$params = [
|
||||
'sql_query' => $_POST['sql_query'],
|
||||
'db' => $_POST['db'] ?? $_GET['db'] ?? null,
|
||||
];
|
||||
|
||||
$this->response->addHTML(MultiTableQuery::displayResults(
|
||||
$params['sql_query'],
|
||||
$params['db'],
|
||||
$PMA_Theme->getImgPath()
|
||||
));
|
||||
}
|
||||
|
||||
public function table(): void
|
||||
{
|
||||
$params = [
|
||||
'tables' => $_GET['tables'],
|
||||
'db' => $_GET['db'] ?? null,
|
||||
];
|
||||
$constrains = $this->dbi->getForeignKeyConstrains(
|
||||
$params['db'],
|
||||
$params['tables']
|
||||
);
|
||||
$this->response->addJSON(['foreignKeyConstrains' => $constrains]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,419 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Database;
|
||||
|
||||
use PhpMyAdmin\Charsets;
|
||||
use PhpMyAdmin\CheckUserPrivileges;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Html\Generator;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\Operations;
|
||||
use PhpMyAdmin\Plugins;
|
||||
use PhpMyAdmin\Plugins\Export\ExportSql;
|
||||
use PhpMyAdmin\Query\Utilities;
|
||||
use PhpMyAdmin\Relation;
|
||||
use PhpMyAdmin\RelationCleanup;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use function count;
|
||||
use function mb_strtolower;
|
||||
use function strlen;
|
||||
|
||||
/**
|
||||
* Handles miscellaneous database operations.
|
||||
*/
|
||||
class OperationsController extends AbstractController
|
||||
{
|
||||
/** @var Operations */
|
||||
private $operations;
|
||||
|
||||
/** @var CheckUserPrivileges */
|
||||
private $checkUserPrivileges;
|
||||
|
||||
/** @var Relation */
|
||||
private $relation;
|
||||
|
||||
/** @var RelationCleanup */
|
||||
private $relationCleanup;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct(
|
||||
$response,
|
||||
Template $template,
|
||||
$db,
|
||||
Operations $operations,
|
||||
CheckUserPrivileges $checkUserPrivileges,
|
||||
Relation $relation,
|
||||
RelationCleanup $relationCleanup,
|
||||
$dbi
|
||||
) {
|
||||
parent::__construct($response, $template, $db);
|
||||
$this->operations = $operations;
|
||||
$this->checkUserPrivileges = $checkUserPrivileges;
|
||||
$this->relation = $relation;
|
||||
$this->relationCleanup = $relationCleanup;
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $cfg, $db, $server, $sql_query, $move, $message, $tables_full, $err_url;
|
||||
global $export_sql_plugin, $views, $sqlConstratints, $local_query, $reload, $url_params, $tables;
|
||||
global $total_num_tables, $sub_part, $tooltip_truename;
|
||||
global $db_collation, $tooltip_aliasname, $pos, $is_information_schema, $single_table, $num_tables;
|
||||
|
||||
$this->checkUserPrivileges->getPrivileges();
|
||||
|
||||
$this->addScriptFiles(['database/operations.js']);
|
||||
|
||||
$sql_query = '';
|
||||
|
||||
/**
|
||||
* Rename/move or copy database
|
||||
*/
|
||||
if (strlen($db) > 0
|
||||
&& (! empty($_POST['db_rename']) || ! empty($_POST['db_copy']))
|
||||
) {
|
||||
if (! empty($_POST['db_rename'])) {
|
||||
$move = true;
|
||||
} else {
|
||||
$move = false;
|
||||
}
|
||||
|
||||
if (! isset($_POST['newname']) || strlen($_POST['newname']) === 0) {
|
||||
$message = Message::error(__('The database name is empty!'));
|
||||
} else {
|
||||
// lower_case_table_names=1 `DB` becomes `db`
|
||||
if ($this->dbi->getLowerCaseNames() === '1') {
|
||||
$_POST['newname'] = mb_strtolower(
|
||||
$_POST['newname']
|
||||
);
|
||||
}
|
||||
|
||||
if ($_POST['newname'] === $_REQUEST['db']) {
|
||||
$message = Message::error(
|
||||
__('Cannot copy database to the same name. Change the name and try again.')
|
||||
);
|
||||
} else {
|
||||
$_error = false;
|
||||
if ($move || ! empty($_POST['create_database_before_copying'])) {
|
||||
$this->operations->createDbBeforeCopy();
|
||||
}
|
||||
|
||||
// here I don't use DELIMITER because it's not part of the
|
||||
// language; I have to send each statement one by one
|
||||
|
||||
// to avoid selecting alternatively the current and new db
|
||||
// we would need to modify the CREATE definitions to qualify
|
||||
// the db name
|
||||
$this->operations->runProcedureAndFunctionDefinitions($db);
|
||||
|
||||
// go back to current db, just in case
|
||||
$this->dbi->selectDb($db);
|
||||
|
||||
$tables_full = $this->dbi->getTablesFull($db);
|
||||
|
||||
// remove all foreign key constraints, otherwise we can get errors
|
||||
/** @var ExportSql $export_sql_plugin */
|
||||
$export_sql_plugin = Plugins::getPlugin(
|
||||
'export',
|
||||
'sql',
|
||||
'libraries/classes/Plugins/Export/',
|
||||
[
|
||||
'single_table' => isset($single_table),
|
||||
'export_type' => 'database',
|
||||
]
|
||||
);
|
||||
|
||||
// create stand-in tables for views
|
||||
$views = $this->operations->getViewsAndCreateSqlViewStandIn(
|
||||
$tables_full,
|
||||
$export_sql_plugin,
|
||||
$db
|
||||
);
|
||||
|
||||
// copy tables
|
||||
$sqlConstratints = $this->operations->copyTables(
|
||||
$tables_full,
|
||||
$move,
|
||||
$db
|
||||
);
|
||||
|
||||
// handle the views
|
||||
if (! $_error) {
|
||||
$this->operations->handleTheViews($views, $move, $db);
|
||||
}
|
||||
unset($views);
|
||||
|
||||
// now that all tables exist, create all the accumulated constraints
|
||||
if (! $_error && count($sqlConstratints) > 0) {
|
||||
$this->operations->createAllAccumulatedConstraints($sqlConstratints);
|
||||
}
|
||||
unset($sqlConstratints);
|
||||
|
||||
if ($this->dbi->getVersion() >= 50100) {
|
||||
// here DELIMITER is not used because it's not part of the
|
||||
// language; each statement is sent one by one
|
||||
|
||||
$this->operations->runEventDefinitionsForDb($db);
|
||||
}
|
||||
|
||||
// go back to current db, just in case
|
||||
$this->dbi->selectDb($db);
|
||||
|
||||
// Duplicate the bookmarks for this db (done once for each db)
|
||||
$this->operations->duplicateBookmarks($_error, $db);
|
||||
|
||||
if (! $_error && $move) {
|
||||
if (isset($_POST['adjust_privileges'])
|
||||
&& ! empty($_POST['adjust_privileges'])
|
||||
) {
|
||||
$this->operations->adjustPrivilegesMoveDb($db, $_POST['newname']);
|
||||
}
|
||||
|
||||
/**
|
||||
* cleanup pmadb stuff for this db
|
||||
*/
|
||||
$this->relationCleanup->database($db);
|
||||
|
||||
// if someday the RENAME DATABASE reappears, do not DROP
|
||||
$local_query = 'DROP DATABASE '
|
||||
. Util::backquote($db) . ';';
|
||||
$sql_query .= "\n" . $local_query;
|
||||
$this->dbi->query($local_query);
|
||||
|
||||
$message = Message::success(
|
||||
__('Database %1$s has been renamed to %2$s.')
|
||||
);
|
||||
$message->addParam($db);
|
||||
$message->addParam($_POST['newname']);
|
||||
} elseif (! $_error) {
|
||||
if (isset($_POST['adjust_privileges'])
|
||||
&& ! empty($_POST['adjust_privileges'])
|
||||
) {
|
||||
$this->operations->adjustPrivilegesCopyDb($db, $_POST['newname']);
|
||||
}
|
||||
|
||||
$message = Message::success(
|
||||
__('Database %1$s has been copied to %2$s.')
|
||||
);
|
||||
$message->addParam($db);
|
||||
$message->addParam($_POST['newname']);
|
||||
} else {
|
||||
$message = Message::error();
|
||||
}
|
||||
$reload = true;
|
||||
|
||||
/* Change database to be used */
|
||||
if (! $_error && $move) {
|
||||
$db = $_POST['newname'];
|
||||
} elseif (! $_error) {
|
||||
if (isset($_POST['switch_to_new'])
|
||||
&& $_POST['switch_to_new'] === 'true'
|
||||
) {
|
||||
$_SESSION['pma_switch_to_new'] = true;
|
||||
$db = $_POST['newname'];
|
||||
} else {
|
||||
$_SESSION['pma_switch_to_new'] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Database has been successfully renamed/moved. If in an Ajax request,
|
||||
* generate the output with {@link Response} and exit
|
||||
*/
|
||||
if ($this->response->isAjax()) {
|
||||
$this->response->setRequestStatus($message->isSuccess());
|
||||
$this->response->addJSON('message', $message);
|
||||
$this->response->addJSON('newname', $_POST['newname']);
|
||||
$this->response->addJSON(
|
||||
'sql_query',
|
||||
Generator::getMessage('', $sql_query)
|
||||
);
|
||||
$this->response->addJSON('db', $db);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Settings for relations stuff
|
||||
*/
|
||||
$cfgRelation = $this->relation->getRelationsParam();
|
||||
|
||||
/**
|
||||
* Check if comments were updated
|
||||
* (must be done before displaying the menu tabs)
|
||||
*/
|
||||
if (isset($_POST['comment'])) {
|
||||
$this->relation->setDbComment($db, $_POST['comment']);
|
||||
}
|
||||
|
||||
Util::checkParameters(['db']);
|
||||
|
||||
$err_url = Util::getScriptNameForOption($cfg['DefaultTabDatabase'], 'database');
|
||||
$err_url .= Url::getCommon(['db' => $db], '&');
|
||||
|
||||
if (! $this->hasDatabase()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$url_params['goto'] = Url::getFromRoute('/database/operations');
|
||||
|
||||
// Gets the database structure
|
||||
$sub_part = '_structure';
|
||||
|
||||
[
|
||||
$tables,
|
||||
$num_tables,
|
||||
$total_num_tables,
|
||||
$sub_part,,
|
||||
$isSystemSchema,
|
||||
$tooltip_truename,
|
||||
$tooltip_aliasname,
|
||||
$pos,
|
||||
] = Util::getDbInfo($db, $sub_part ?? '');
|
||||
|
||||
$oldMessage = '';
|
||||
if (isset($message)) {
|
||||
$oldMessage = Generator::getMessage($message, $sql_query);
|
||||
unset($message);
|
||||
}
|
||||
|
||||
$db_collation = $this->dbi->getDbCollation($db);
|
||||
$is_information_schema = Utilities::isSystemSchema($db);
|
||||
|
||||
if ($is_information_schema) {
|
||||
return;
|
||||
}
|
||||
|
||||
$databaseComment = '';
|
||||
if ($cfgRelation['commwork']) {
|
||||
$databaseComment = $this->relation->getDbComment($db);
|
||||
}
|
||||
|
||||
$hasAdjustPrivileges = $GLOBALS['db_priv'] && $GLOBALS['table_priv']
|
||||
&& $GLOBALS['col_priv'] && $GLOBALS['proc_priv'] && $GLOBALS['is_reload_priv'];
|
||||
|
||||
$isDropDatabaseAllowed = ($this->dbi->isSuperUser() || $cfg['AllowUserDropDatabase'])
|
||||
&& ! $isSystemSchema && $db !== 'mysql';
|
||||
|
||||
$switchToNew = isset($_SESSION['pma_switch_to_new']) && $_SESSION['pma_switch_to_new'];
|
||||
|
||||
$charsets = Charsets::getCharsets($this->dbi, $GLOBALS['cfg']['Server']['DisableIS']);
|
||||
$collations = Charsets::getCollations($this->dbi, $GLOBALS['cfg']['Server']['DisableIS']);
|
||||
|
||||
if (! $cfgRelation['allworks']
|
||||
&& $cfg['PmaNoRelation_DisableWarning'] == false
|
||||
) {
|
||||
$message = Message::notice(
|
||||
__(
|
||||
'The phpMyAdmin configuration storage has been deactivated. ' .
|
||||
'%sFind out why%s.'
|
||||
)
|
||||
);
|
||||
$message->addParamHtml(
|
||||
'<a href="' . Url::getFromRoute('/check-relations')
|
||||
. '" data-post="' . Url::getCommon(['db' => $db]) . '">'
|
||||
);
|
||||
$message->addParamHtml('</a>');
|
||||
/* Show error if user has configured something, notice elsewhere */
|
||||
if (! empty($cfg['Servers'][$server]['pmadb'])) {
|
||||
$message->isError(true);
|
||||
}
|
||||
}
|
||||
|
||||
$this->render('database/operations/index', [
|
||||
'message' => $oldMessage,
|
||||
'db' => $db,
|
||||
'has_comment' => $cfgRelation['commwork'],
|
||||
'db_comment' => $databaseComment,
|
||||
'db_collation' => $db_collation,
|
||||
'has_adjust_privileges' => $hasAdjustPrivileges,
|
||||
'is_drop_database_allowed' => $isDropDatabaseAllowed,
|
||||
'switch_to_new' => $switchToNew,
|
||||
'charsets' => $charsets,
|
||||
'collations' => $collations,
|
||||
]);
|
||||
}
|
||||
|
||||
public function collation(): void
|
||||
{
|
||||
global $db, $cfg, $err_url;
|
||||
|
||||
if (! $this->response->isAjax()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (empty($_POST['db_collation'])) {
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON('message', Message::error(__('No collation provided.')));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Util::checkParameters(['db']);
|
||||
|
||||
$err_url = Util::getScriptNameForOption($cfg['DefaultTabDatabase'], 'database');
|
||||
$err_url .= Url::getCommon(['db' => $db], '&');
|
||||
|
||||
if (! $this->hasDatabase()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sql_query = 'ALTER DATABASE ' . Util::backquote($db)
|
||||
. ' DEFAULT' . Util::getCharsetQueryPart($_POST['db_collation'] ?? '');
|
||||
$this->dbi->query($sql_query);
|
||||
$message = Message::success();
|
||||
|
||||
/**
|
||||
* Changes tables charset if requested by the user
|
||||
*/
|
||||
if (isset($_POST['change_all_tables_collations']) &&
|
||||
$_POST['change_all_tables_collations'] === 'on'
|
||||
) {
|
||||
[$tables] = Util::getDbInfo($db, null);
|
||||
foreach ($tables as $tableName => $data) {
|
||||
if ($this->dbi->getTable($db, $tableName)->isView()) {
|
||||
// Skip views, we can not change the collation of a view.
|
||||
// issue #15283
|
||||
continue;
|
||||
}
|
||||
$sql_query = 'ALTER TABLE '
|
||||
. Util::backquote($db)
|
||||
. '.'
|
||||
. Util::backquote($tableName)
|
||||
. ' DEFAULT '
|
||||
. Util::getCharsetQueryPart($_POST['db_collation'] ?? '');
|
||||
$this->dbi->query($sql_query);
|
||||
|
||||
/**
|
||||
* Changes columns charset if requested by the user
|
||||
*/
|
||||
if (! isset($_POST['change_all_tables_columns_collations']) ||
|
||||
$_POST['change_all_tables_columns_collations'] !== 'on'
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->operations->changeAllColumnsCollation($db, $tableName, $_POST['db_collation']);
|
||||
}
|
||||
}
|
||||
|
||||
$this->response->setRequestStatus($message->isSuccess());
|
||||
$this->response->addJSON('message', $message);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
/**
|
||||
* Controller for database privileges
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Database;
|
||||
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Server\Privileges;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Util;
|
||||
|
||||
/**
|
||||
* Controller for database privileges
|
||||
*/
|
||||
class PrivilegesController extends AbstractController
|
||||
{
|
||||
/** @var Privileges */
|
||||
private $privileges;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $db, Privileges $privileges, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template, $db);
|
||||
$this->privileges = $privileges;
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $params Request parameters
|
||||
*/
|
||||
public function index(array $params): string
|
||||
{
|
||||
global $cfg, $text_dir, $PMA_Theme;
|
||||
|
||||
$scriptName = Util::getScriptNameForOption(
|
||||
$cfg['DefaultTabDatabase'],
|
||||
'database'
|
||||
);
|
||||
|
||||
$privileges = [];
|
||||
if ($this->dbi->isSuperUser()) {
|
||||
$privileges = $this->privileges->getAllPrivileges($params['checkprivsdb']);
|
||||
}
|
||||
|
||||
return $this->template->render('database/privileges/index', [
|
||||
'is_superuser' => $this->dbi->isSuperUser(),
|
||||
'db' => $params['checkprivsdb'],
|
||||
'database_url' => $scriptName,
|
||||
'theme_image_path' => $PMA_Theme->getImgPath(),
|
||||
'text_dir' => $text_dir,
|
||||
'is_createuser' => $this->dbi->isCreateUser(),
|
||||
'is_grantuser' => $this->dbi->isGrantUser(),
|
||||
'privileges' => $privileges,
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,169 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Database;
|
||||
|
||||
use PhpMyAdmin\Database\Qbe;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Operations;
|
||||
use PhpMyAdmin\Relation;
|
||||
use PhpMyAdmin\RelationCleanup;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\SavedSearches;
|
||||
use PhpMyAdmin\Sql;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Transformations;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use function stripos;
|
||||
|
||||
class QueryByExampleController extends AbstractController
|
||||
{
|
||||
/** @var Relation */
|
||||
private $relation;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $db, Relation $relation, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template, $db);
|
||||
$this->relation = $relation;
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $db, $savedSearchList, $savedSearch, $currentSearchId, $PMA_Theme;
|
||||
global $sql_query, $goto, $sub_part, $tables, $num_tables, $total_num_tables;
|
||||
global $tooltip_truename, $tooltip_aliasname, $pos, $url_params, $cfg, $err_url;
|
||||
|
||||
// Gets the relation settings
|
||||
$cfgRelation = $this->relation->getRelationsParam();
|
||||
|
||||
$savedSearchList = [];
|
||||
$savedSearch = null;
|
||||
$currentSearchId = null;
|
||||
$this->addScriptFiles(['database/qbe.js']);
|
||||
if ($cfgRelation['savedsearcheswork']) {
|
||||
//Get saved search list.
|
||||
$savedSearch = new SavedSearches($GLOBALS, $this->relation);
|
||||
$savedSearch->setUsername($GLOBALS['cfg']['Server']['user'])
|
||||
->setDbname($db);
|
||||
|
||||
if (! empty($_POST['searchId'])) {
|
||||
$savedSearch->setId($_POST['searchId']);
|
||||
}
|
||||
|
||||
//Action field is sent.
|
||||
if (isset($_POST['action'])) {
|
||||
$savedSearch->setSearchName($_POST['searchName']);
|
||||
if ($_POST['action'] === 'create') {
|
||||
$saveResult = $savedSearch->setId(null)
|
||||
->setCriterias($_POST)
|
||||
->save();
|
||||
} elseif ($_POST['action'] === 'update') {
|
||||
$saveResult = $savedSearch->setCriterias($_POST)
|
||||
->save();
|
||||
} elseif ($_POST['action'] === 'delete') {
|
||||
$deleteResult = $savedSearch->delete();
|
||||
//After deletion, reset search.
|
||||
$savedSearch = new SavedSearches($GLOBALS, $this->relation);
|
||||
$savedSearch->setUsername($GLOBALS['cfg']['Server']['user'])
|
||||
->setDbname($db);
|
||||
$_POST = [];
|
||||
} elseif ($_POST['action'] === 'load') {
|
||||
if (empty($_POST['searchId'])) {
|
||||
//when not loading a search, reset the object.
|
||||
$savedSearch = new SavedSearches($GLOBALS, $this->relation);
|
||||
$savedSearch->setUsername($GLOBALS['cfg']['Server']['user'])
|
||||
->setDbname($db);
|
||||
$_POST = [];
|
||||
} else {
|
||||
$loadResult = $savedSearch->load();
|
||||
}
|
||||
}
|
||||
//Else, it's an "update query"
|
||||
}
|
||||
|
||||
$savedSearchList = $savedSearch->getList();
|
||||
$currentSearchId = $savedSearch->getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* A query has been submitted -> (maybe) execute it
|
||||
*/
|
||||
$hasMessageToDisplay = false;
|
||||
if (isset($_POST['submit_sql']) && ! empty($sql_query)) {
|
||||
if (stripos($sql_query, 'SELECT') !== 0) {
|
||||
$hasMessageToDisplay = true;
|
||||
} else {
|
||||
$goto = Url::getFromRoute('/database/sql');
|
||||
|
||||
$sql = new Sql(
|
||||
$this->dbi,
|
||||
$this->relation,
|
||||
new RelationCleanup($this->dbi, $this->relation),
|
||||
new Operations($this->dbi, $this->relation),
|
||||
new Transformations(),
|
||||
$this->template
|
||||
);
|
||||
|
||||
$this->response->addHTML($sql->executeQueryAndSendQueryResponse(
|
||||
null, // analyzed_sql_results
|
||||
false, // is_gotofile
|
||||
$_POST['db'], // db
|
||||
null, // table
|
||||
false, // find_real_end
|
||||
null, // sql_query_for_bookmark
|
||||
null, // extra_data
|
||||
null, // message_to_show
|
||||
null, // sql_data
|
||||
$goto, // goto
|
||||
$PMA_Theme->getImgPath(),
|
||||
null, // disp_query
|
||||
null, // disp_message
|
||||
$sql_query, // sql_query
|
||||
null // complete_query
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
$sub_part = '_qbe';
|
||||
|
||||
Util::checkParameters(['db']);
|
||||
|
||||
$err_url = Util::getScriptNameForOption($cfg['DefaultTabDatabase'], 'database');
|
||||
$err_url .= Url::getCommon(['db' => $db], '&');
|
||||
|
||||
if (! $this->hasDatabase()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$url_params['goto'] = Url::getFromRoute('/database/qbe');
|
||||
|
||||
[
|
||||
$tables,
|
||||
$num_tables,
|
||||
$total_num_tables,
|
||||
$sub_part,,,
|
||||
$tooltip_truename,
|
||||
$tooltip_aliasname,
|
||||
$pos,
|
||||
] = Util::getDbInfo($db, $sub_part ?? '');
|
||||
|
||||
$databaseQbe = new Qbe($this->relation, $this->template, $this->dbi, $db, $savedSearchList, $savedSearch);
|
||||
|
||||
$this->render('database/qbe/index', [
|
||||
'url_params' => $url_params,
|
||||
'has_message_to_display' => $hasMessageToDisplay,
|
||||
'selection_form_html' => $databaseQbe->getSelectionForm(),
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Database;
|
||||
|
||||
use PhpMyAdmin\CheckUserPrivileges;
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\Database\Routines;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\DbTableExists;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use function in_array;
|
||||
use function strlen;
|
||||
|
||||
/**
|
||||
* Routines management.
|
||||
*/
|
||||
class RoutinesController extends AbstractController
|
||||
{
|
||||
/** @var CheckUserPrivileges */
|
||||
private $checkUserPrivileges;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $db, CheckUserPrivileges $checkUserPrivileges, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template, $db);
|
||||
$this->checkUserPrivileges = $checkUserPrivileges;
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $db, $table, $tables, $num_tables, $total_num_tables, $sub_part;
|
||||
global $tooltip_truename, $tooltip_aliasname, $pos;
|
||||
global $errors, $PMA_Theme, $text_dir, $err_url, $url_params, $cfg;
|
||||
|
||||
$type = $_REQUEST['type'] ?? null;
|
||||
|
||||
$this->checkUserPrivileges->getPrivileges();
|
||||
|
||||
if (! $this->response->isAjax()) {
|
||||
/**
|
||||
* Displays the header and tabs
|
||||
*/
|
||||
if (! empty($table) && in_array($table, $this->dbi->getTables($db))) {
|
||||
Util::checkParameters(['db', 'table']);
|
||||
|
||||
$url_params = ['db' => $db, 'table' => $table];
|
||||
$err_url = Util::getScriptNameForOption($cfg['DefaultTabTable'], 'table');
|
||||
$err_url .= Url::getCommon($url_params, '&');
|
||||
|
||||
DbTableExists::check();
|
||||
} else {
|
||||
$table = '';
|
||||
|
||||
Util::checkParameters(['db']);
|
||||
|
||||
$err_url = Util::getScriptNameForOption($cfg['DefaultTabDatabase'], 'database');
|
||||
$err_url .= Url::getCommon(['db' => $db], '&');
|
||||
|
||||
if (! $this->hasDatabase()) {
|
||||
return;
|
||||
}
|
||||
|
||||
[
|
||||
$tables,
|
||||
$num_tables,
|
||||
$total_num_tables,
|
||||
$sub_part,,,
|
||||
$tooltip_truename,
|
||||
$tooltip_aliasname,
|
||||
$pos,
|
||||
] = Util::getDbInfo($db, $sub_part ?? '');
|
||||
}
|
||||
} elseif (strlen($db) > 0) {
|
||||
$this->dbi->selectDb($db);
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep a list of errors that occurred while
|
||||
* processing an 'Add' or 'Edit' operation.
|
||||
*/
|
||||
$errors = [];
|
||||
|
||||
$routines = new Routines($this->dbi, $this->template, $this->response);
|
||||
|
||||
$routines->handleEditor();
|
||||
$routines->handleExecute();
|
||||
$routines->export();
|
||||
|
||||
if (! Core::isValid($type, ['FUNCTION', 'PROCEDURE'])) {
|
||||
$type = null;
|
||||
}
|
||||
|
||||
$items = $this->dbi->getRoutines($db, $type);
|
||||
$isAjax = $this->response->isAjax() && empty($_REQUEST['ajax_page_request']);
|
||||
|
||||
$rows = '';
|
||||
foreach ($items as $item) {
|
||||
$rows .= $routines->getRow($item, $isAjax ? 'ajaxInsert hide' : '');
|
||||
}
|
||||
|
||||
$this->render('database/routines/index', [
|
||||
'db' => $db,
|
||||
'table' => $table,
|
||||
'items' => $items,
|
||||
'rows' => $rows,
|
||||
'select_all_arrow_src' => $PMA_Theme->getImgPath() . 'arrow_' . $text_dir . '.png',
|
||||
'has_privilege' => Util::currentUserHasPrivilege('CREATE ROUTINE', $db, $table),
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Database;
|
||||
|
||||
use PhpMyAdmin\Database\Search;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Html\Generator;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
|
||||
class SearchController extends AbstractController
|
||||
{
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name.
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $db, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template, $db);
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $cfg, $db, $err_url, $url_params, $tables, $num_tables, $total_num_tables, $sub_part;
|
||||
global $tooltip_truename, $tooltip_aliasname, $pos;
|
||||
|
||||
$this->addScriptFiles([
|
||||
'database/search.js',
|
||||
'vendor/stickyfill.min.js',
|
||||
'sql.js',
|
||||
'makegrid.js',
|
||||
]);
|
||||
|
||||
Util::checkParameters(['db']);
|
||||
|
||||
$err_url = Util::getScriptNameForOption($cfg['DefaultTabDatabase'], 'database');
|
||||
$err_url .= Url::getCommon(['db' => $db], '&');
|
||||
|
||||
if (! $this->hasDatabase()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If config variable $cfg['UseDbSearch'] is on false : exit.
|
||||
if (! $cfg['UseDbSearch']) {
|
||||
Generator::mysqlDie(
|
||||
__('Access denied!'),
|
||||
'',
|
||||
false,
|
||||
$err_url
|
||||
);
|
||||
}
|
||||
$url_params['goto'] = Url::getFromRoute('/database/search');
|
||||
|
||||
// Create a database search instance
|
||||
$databaseSearch = new Search($this->dbi, $db, $this->template);
|
||||
|
||||
// Display top links if we are not in an Ajax request
|
||||
if (! $this->response->isAjax()) {
|
||||
[
|
||||
$tables,
|
||||
$num_tables,
|
||||
$total_num_tables,
|
||||
$sub_part,,,
|
||||
$tooltip_truename,
|
||||
$tooltip_aliasname,
|
||||
$pos,
|
||||
] = Util::getDbInfo($db, $sub_part ?? '');
|
||||
}
|
||||
|
||||
// Main search form has been submitted, get results
|
||||
if (isset($_POST['submit_search'])) {
|
||||
$this->response->addHTML($databaseSearch->getSearchResults());
|
||||
}
|
||||
|
||||
// If we are in an Ajax request, we need to exit after displaying all the HTML
|
||||
if ($this->response->isAjax() && empty($_REQUEST['ajax_page_request'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Display the search form
|
||||
$this->response->addHTML($databaseSearch->getMainHtml());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Database;
|
||||
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use function json_encode;
|
||||
|
||||
/**
|
||||
* Table/Column autocomplete in SQL editors.
|
||||
*/
|
||||
class SqlAutoCompleteController extends AbstractController
|
||||
{
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name.
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $db, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template, $db);
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $cfg, $db, $sql_autocomplete;
|
||||
|
||||
$sql_autocomplete = true;
|
||||
if ($cfg['EnableAutocompleteForTablesAndColumns']) {
|
||||
$db = $_POST['db'] ?? $db;
|
||||
$sql_autocomplete = [];
|
||||
if ($db) {
|
||||
$tableNames = $this->dbi->getTables($db);
|
||||
foreach ($tableNames as $tableName) {
|
||||
$sql_autocomplete[$tableName] = $this->dbi->getColumns(
|
||||
$db,
|
||||
$tableName
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->response->addJSON(['tables' => json_encode($sql_autocomplete)]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Database;
|
||||
|
||||
use PhpMyAdmin\Config\PageSettings;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\SqlQueryForm;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use function htmlspecialchars;
|
||||
|
||||
/**
|
||||
* Database SQL executor
|
||||
*/
|
||||
class SqlController extends AbstractController
|
||||
{
|
||||
/** @var SqlQueryForm */
|
||||
private $sqlQueryForm;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name
|
||||
*/
|
||||
public function __construct($response, Template $template, $db, SqlQueryForm $sqlQueryForm)
|
||||
{
|
||||
parent::__construct($response, $template, $db);
|
||||
$this->sqlQueryForm = $sqlQueryForm;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $goto, $back, $db, $cfg, $err_url;
|
||||
|
||||
$this->addScriptFiles([
|
||||
'makegrid.js',
|
||||
'vendor/jquery/jquery.uitablefilter.js',
|
||||
'vendor/stickyfill.min.js',
|
||||
'sql.js',
|
||||
]);
|
||||
|
||||
$pageSettings = new PageSettings('Sql');
|
||||
$this->response->addHTML($pageSettings->getErrorHTML());
|
||||
$this->response->addHTML($pageSettings->getHTML());
|
||||
|
||||
Util::checkParameters(['db']);
|
||||
|
||||
$err_url = Util::getScriptNameForOption($cfg['DefaultTabDatabase'], 'database');
|
||||
$err_url .= Url::getCommon(['db' => $db], '&');
|
||||
|
||||
if (! $this->hasDatabase()) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* After a syntax error, we return to this script
|
||||
* with the typed query in the textarea.
|
||||
*/
|
||||
$goto = Url::getFromRoute('/database/sql');
|
||||
$back = $goto;
|
||||
|
||||
$this->response->addHTML($this->sqlQueryForm->getHtml(
|
||||
true,
|
||||
false,
|
||||
isset($_POST['delimiter'])
|
||||
? htmlspecialchars($_POST['delimiter'])
|
||||
: ';'
|
||||
));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Database;
|
||||
|
||||
use PhpMyAdmin\SqlParser\Utils\Formatter;
|
||||
use function strlen;
|
||||
|
||||
/**
|
||||
* Format SQL for SQL editors.
|
||||
*/
|
||||
class SqlFormatController extends AbstractController
|
||||
{
|
||||
public function index(): void
|
||||
{
|
||||
$params = ['sql' => $_POST['sql'] ?? null];
|
||||
$query = strlen((string) $params['sql']) > 0 ? $params['sql'] : '';
|
||||
$this->response->addJSON(['sql' => Formatter::format($query)]);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,159 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Database;
|
||||
|
||||
use PhpMyAdmin\CheckUserPrivileges;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Html\Generator;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Tracker;
|
||||
use PhpMyAdmin\Tracking;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use function count;
|
||||
use function htmlspecialchars;
|
||||
use function sprintf;
|
||||
|
||||
/**
|
||||
* Tracking configuration for database.
|
||||
*/
|
||||
class TrackingController extends AbstractController
|
||||
{
|
||||
/** @var Tracking */
|
||||
private $tracking;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name.
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $db, Tracking $tracking, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template, $db);
|
||||
$this->tracking = $tracking;
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $db, $text_dir, $url_params, $tables, $num_tables, $PMA_Theme;
|
||||
global $total_num_tables, $sub_part, $pos, $data, $cfg;
|
||||
global $tooltip_truename, $tooltip_aliasname, $err_url;
|
||||
|
||||
$this->addScriptFiles(['vendor/jquery/jquery.tablesorter.js', 'database/tracking.js']);
|
||||
|
||||
Util::checkParameters(['db']);
|
||||
|
||||
$err_url = Util::getScriptNameForOption($cfg['DefaultTabDatabase'], 'database');
|
||||
$err_url .= Url::getCommon(['db' => $db], '&');
|
||||
|
||||
if (! $this->hasDatabase()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$url_params['goto'] = Url::getFromRoute('/table/tracking');
|
||||
$url_params['back'] = Url::getFromRoute('/database/tracking');
|
||||
|
||||
// Get the database structure
|
||||
$sub_part = '_structure';
|
||||
|
||||
[
|
||||
$tables,
|
||||
$num_tables,
|
||||
$total_num_tables,
|
||||
$sub_part,,
|
||||
$isSystemSchema,
|
||||
$tooltip_truename,
|
||||
$tooltip_aliasname,
|
||||
$pos,
|
||||
] = Util::getDbInfo($db, $sub_part ?? '');
|
||||
|
||||
if (isset($_POST['delete_tracking'], $_POST['table'])) {
|
||||
Tracker::deleteTracking($db, $_POST['table']);
|
||||
echo Message::success(
|
||||
__('Tracking data deleted successfully.')
|
||||
)->getDisplay();
|
||||
} elseif (isset($_POST['submit_create_version'])) {
|
||||
$this->tracking->createTrackingForMultipleTables($_POST['selected']);
|
||||
echo Message::success(
|
||||
sprintf(
|
||||
__(
|
||||
'Version %1$s was created for selected tables,'
|
||||
. ' tracking is active for them.'
|
||||
),
|
||||
htmlspecialchars($_POST['version'])
|
||||
)
|
||||
)->getDisplay();
|
||||
} elseif (isset($_POST['submit_mult'])) {
|
||||
if (! empty($_POST['selected_tbl'])) {
|
||||
if ($_POST['submit_mult'] === 'delete_tracking') {
|
||||
foreach ($_POST['selected_tbl'] as $table) {
|
||||
Tracker::deleteTracking($db, $table);
|
||||
}
|
||||
echo Message::success(
|
||||
__('Tracking data deleted successfully.')
|
||||
)->getDisplay();
|
||||
} elseif ($_POST['submit_mult'] === 'track') {
|
||||
echo $this->template->render('create_tracking_version', [
|
||||
'route' => '/database/tracking',
|
||||
'url_params' => $url_params,
|
||||
'last_version' => 0,
|
||||
'db' => $db,
|
||||
'selected' => $_POST['selected_tbl'],
|
||||
'type' => 'both',
|
||||
'default_statements' => $cfg['Server']['tracking_default_statements'],
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
echo Message::notice(
|
||||
__('No tables selected.')
|
||||
)->getDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
// Get tracked data about the database
|
||||
$data = Tracker::getTrackedData($db, '', '1');
|
||||
|
||||
// No tables present and no log exist
|
||||
if ($num_tables == 0 && count($data['ddlog']) === 0) {
|
||||
echo '<p>' , __('No tables found in database.') , '</p>' , "\n";
|
||||
|
||||
if (empty($isSystemSchema)) {
|
||||
$checkUserPrivileges = new CheckUserPrivileges($this->dbi);
|
||||
$checkUserPrivileges->getPrivileges();
|
||||
|
||||
echo $this->template->render('database/create_table', ['db' => $db]);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
echo $this->tracking->getHtmlForDbTrackingTables(
|
||||
$db,
|
||||
$url_params,
|
||||
$PMA_Theme->getImgPath(),
|
||||
$text_dir
|
||||
);
|
||||
|
||||
// If available print out database log
|
||||
if (count($data['ddlog']) <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$log = '';
|
||||
foreach ($data['ddlog'] as $entry) {
|
||||
$log .= '# ' . $entry['date'] . ' ' . $entry['username'] . "\n"
|
||||
. $entry['statement'] . "\n";
|
||||
}
|
||||
echo Generator::getMessage(__('Database Log'), $log);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Database;
|
||||
|
||||
use PhpMyAdmin\Database\Triggers;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\DbTableExists;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use function in_array;
|
||||
use function strlen;
|
||||
|
||||
/**
|
||||
* Triggers management.
|
||||
*/
|
||||
class TriggersController extends AbstractController
|
||||
{
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name.
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $db, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template, $db);
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $db, $table, $tables, $num_tables, $total_num_tables, $sub_part;
|
||||
global $tooltip_truename, $tooltip_aliasname, $pos;
|
||||
global $errors, $url_params, $err_url, $cfg;
|
||||
|
||||
if (! $this->response->isAjax()) {
|
||||
/**
|
||||
* Displays the header and tabs
|
||||
*/
|
||||
if (! empty($table) && in_array($table, $this->dbi->getTables($db))) {
|
||||
Util::checkParameters(['db', 'table']);
|
||||
|
||||
$url_params = ['db' => $db, 'table' => $table];
|
||||
$err_url = Util::getScriptNameForOption($cfg['DefaultTabTable'], 'table');
|
||||
$err_url .= Url::getCommon($url_params, '&');
|
||||
|
||||
DbTableExists::check();
|
||||
} else {
|
||||
$table = '';
|
||||
|
||||
Util::checkParameters(['db']);
|
||||
|
||||
$err_url = Util::getScriptNameForOption($cfg['DefaultTabDatabase'], 'database');
|
||||
$err_url .= Url::getCommon(['db' => $db], '&');
|
||||
|
||||
if (! $this->hasDatabase()) {
|
||||
return;
|
||||
}
|
||||
|
||||
[
|
||||
$tables,
|
||||
$num_tables,
|
||||
$total_num_tables,
|
||||
$sub_part,,,
|
||||
$tooltip_truename,
|
||||
$tooltip_aliasname,
|
||||
$pos,
|
||||
] = Util::getDbInfo($db, $sub_part ?? '');
|
||||
}
|
||||
} elseif (strlen($db) > 0) {
|
||||
$this->dbi->selectDb($db);
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep a list of errors that occurred while
|
||||
* processing an 'Add' or 'Edit' operation.
|
||||
*/
|
||||
$errors = [];
|
||||
|
||||
$triggers = new Triggers($this->dbi, $this->template, $this->response);
|
||||
$triggers->main();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers;
|
||||
|
||||
final class DatabaseController extends AbstractController
|
||||
{
|
||||
public function all(): void
|
||||
{
|
||||
global $dblist;
|
||||
|
||||
$this->response->addJSON(['databases' => $dblist->databases]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,163 @@
|
|||
<?php
|
||||
/**
|
||||
* Handle error report submission
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers;
|
||||
|
||||
use PhpMyAdmin\ErrorHandler;
|
||||
use PhpMyAdmin\ErrorReport;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\UserPreferences;
|
||||
use function count;
|
||||
use function in_array;
|
||||
use function is_string;
|
||||
use function json_decode;
|
||||
use function time;
|
||||
|
||||
/**
|
||||
* Handle error report submission
|
||||
*/
|
||||
class ErrorReportController extends AbstractController
|
||||
{
|
||||
/** @var ErrorReport */
|
||||
private $errorReport;
|
||||
|
||||
/** @var ErrorHandler */
|
||||
private $errorHandler;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
*/
|
||||
public function __construct(
|
||||
$response,
|
||||
Template $template,
|
||||
ErrorReport $errorReport,
|
||||
ErrorHandler $errorHandler
|
||||
) {
|
||||
parent::__construct($response, $template);
|
||||
$this->errorReport = $errorReport;
|
||||
$this->errorHandler = $errorHandler;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $cfg;
|
||||
|
||||
if (! isset($_POST['exception_type'])
|
||||
|| ! in_array($_POST['exception_type'], ['js', 'php'])
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($_POST['send_error_report'])
|
||||
&& ($_POST['send_error_report'] == true
|
||||
|| $_POST['send_error_report'] == '1')
|
||||
) {
|
||||
if ($_POST['exception_type'] === 'php') {
|
||||
/**
|
||||
* Prevent infinite error submission.
|
||||
* Happens in case error submissions fails.
|
||||
* If reporting is done in some time interval,
|
||||
* just clear them & clear json data too.
|
||||
*/
|
||||
if (isset($_SESSION['prev_error_subm_time'], $_SESSION['error_subm_count'])
|
||||
&& $_SESSION['error_subm_count'] >= 3
|
||||
&& ($_SESSION['prev_error_subm_time'] - time()) <= 3000
|
||||
) {
|
||||
$_SESSION['error_subm_count'] = 0;
|
||||
$_SESSION['prev_errors'] = '';
|
||||
$this->response->addJSON('stopErrorReportLoop', '1');
|
||||
} else {
|
||||
$_SESSION['prev_error_subm_time'] = time();
|
||||
$_SESSION['error_subm_count'] = isset($_SESSION['error_subm_count'])
|
||||
? $_SESSION['error_subm_count'] + 1
|
||||
: 0;
|
||||
}
|
||||
}
|
||||
$reportData = $this->errorReport->getData($_POST['exception_type']);
|
||||
// report if and only if there were 'actual' errors.
|
||||
if (count($reportData) > 0) {
|
||||
$server_response = $this->errorReport->send($reportData);
|
||||
if (! is_string($server_response)) {
|
||||
$success = false;
|
||||
} else {
|
||||
$decoded_response = json_decode($server_response, true);
|
||||
$success = ! empty($decoded_response) ?
|
||||
$decoded_response['success'] : false;
|
||||
}
|
||||
|
||||
/* Message to show to the user */
|
||||
if ($success) {
|
||||
if ((isset($_POST['automatic'])
|
||||
&& $_POST['automatic'] === 'true')
|
||||
|| $cfg['SendErrorReports'] === 'always'
|
||||
) {
|
||||
$msg = __(
|
||||
'An error has been detected and an error report has been '
|
||||
. 'automatically submitted based on your settings.'
|
||||
);
|
||||
} else {
|
||||
$msg = __('Thank you for submitting this report.');
|
||||
}
|
||||
} else {
|
||||
$msg = __(
|
||||
'An error has been detected and an error report has been '
|
||||
. 'generated but failed to be sent.'
|
||||
);
|
||||
$msg .= ' ';
|
||||
$msg .= __(
|
||||
'If you experience any '
|
||||
. 'problems please submit a bug report manually.'
|
||||
);
|
||||
}
|
||||
$msg .= ' ' . __('You may want to refresh the page.');
|
||||
|
||||
/* Create message object */
|
||||
if ($success) {
|
||||
$msg = Message::notice($msg);
|
||||
} else {
|
||||
$msg = Message::error($msg);
|
||||
}
|
||||
|
||||
/* Add message to response */
|
||||
if ($this->response->isAjax()) {
|
||||
if ($_POST['exception_type'] === 'js') {
|
||||
$this->response->addJSON('message', $msg);
|
||||
} else {
|
||||
$this->response->addJSON('errSubmitMsg', $msg);
|
||||
}
|
||||
} elseif ($_POST['exception_type'] === 'php') {
|
||||
$jsCode = 'Functions.ajaxShowMessage(\'<div class="alert alert-danger" role="alert">'
|
||||
. $msg
|
||||
. '</div>\', false);';
|
||||
$this->response->getFooter()->getScripts()->addCode($jsCode);
|
||||
}
|
||||
|
||||
if ($_POST['exception_type'] === 'php') {
|
||||
// clear previous errors & save new ones.
|
||||
$this->errorHandler->savePreviousErrors();
|
||||
}
|
||||
|
||||
/* Persist always send settings */
|
||||
if (isset($_POST['always_send'])
|
||||
&& $_POST['always_send'] === 'true'
|
||||
) {
|
||||
$userPreferences = new UserPreferences();
|
||||
$userPreferences->persistOption('SendErrorReports', 'always', 'ask');
|
||||
}
|
||||
}
|
||||
} elseif (! empty($_POST['get_settings'])) {
|
||||
$this->response->addJSON('report_setting', $cfg['SendErrorReports']);
|
||||
} elseif ($_POST['exception_type'] === 'js') {
|
||||
$this->response->addHTML($this->errorReport->getForm());
|
||||
} else {
|
||||
// clear previous errors & save new ones.
|
||||
$this->errorHandler->savePreviousErrors();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,704 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers;
|
||||
|
||||
use PhpMyAdmin\Controllers\Database\ExportController as DatabaseExportController;
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\Encoding;
|
||||
use PhpMyAdmin\Exceptions\ExportException;
|
||||
use PhpMyAdmin\Export;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\Plugins;
|
||||
use PhpMyAdmin\Plugins\ExportPlugin;
|
||||
use PhpMyAdmin\Relation;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Sanitize;
|
||||
use PhpMyAdmin\SqlParser\Parser;
|
||||
use PhpMyAdmin\SqlParser\Statements\SelectStatement;
|
||||
use PhpMyAdmin\SqlParser\Utils\Misc;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use const PHP_EOL;
|
||||
use function count;
|
||||
use function function_exists;
|
||||
use function in_array;
|
||||
use function ini_set;
|
||||
use function is_array;
|
||||
use function ob_end_clean;
|
||||
use function ob_get_length;
|
||||
use function ob_get_level;
|
||||
use function register_shutdown_function;
|
||||
use function strlen;
|
||||
use function time;
|
||||
|
||||
final class ExportController extends AbstractController
|
||||
{
|
||||
/** @var Export */
|
||||
private $export;
|
||||
|
||||
/** @var Relation */
|
||||
private $relation;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
*/
|
||||
public function __construct($response, Template $template, Export $export, Relation $relation)
|
||||
{
|
||||
parent::__construct($response, $template);
|
||||
$this->export = $export;
|
||||
$this->relation = $relation;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $containerBuilder, $db, $export_type, $filename_template, $sql_query, $err_url, $message;
|
||||
global $compression, $crlf, $asfile, $buffer_needed, $save_on_server, $file_handle, $separate_files;
|
||||
global $output_charset_conversion, $output_kanji_conversion, $table, $what, $export_plugin, $single_table;
|
||||
global $compression_methods, $onserver, $back_button, $refreshButton, $save_filename, $filename;
|
||||
global $quick_export, $cfg, $tables, $table_select, $aliases, $dump_buffer, $dump_buffer_len;
|
||||
global $time_start, $charset, $remember_template, $mime_type, $num_tables, $dump_buffer_objects;
|
||||
global $active_page, $do_relation, $do_comments, $do_mime, $do_dates, $whatStrucOrData, $db_select;
|
||||
global $table_structure, $table_data, $lock_tables, $allrows, $limit_to, $limit_from;
|
||||
|
||||
$this->addScriptFiles(['export_output.js']);
|
||||
|
||||
/**
|
||||
* Sets globals from $_POST
|
||||
*
|
||||
* - Please keep the parameters in order of their appearance in the form
|
||||
* - Some of these parameters are not used, as the code below directly
|
||||
* verifies from the superglobal $_POST or $_REQUEST
|
||||
* TODO: this should be removed to avoid passing user input to GLOBALS
|
||||
* without checking
|
||||
*/
|
||||
$post_params = [
|
||||
'db',
|
||||
'table',
|
||||
'what',
|
||||
'single_table',
|
||||
'export_type',
|
||||
'export_method',
|
||||
'quick_or_custom',
|
||||
'db_select',
|
||||
'table_select',
|
||||
'table_structure',
|
||||
'table_data',
|
||||
'limit_to',
|
||||
'limit_from',
|
||||
'allrows',
|
||||
'lock_tables',
|
||||
'output_format',
|
||||
'filename_template',
|
||||
'maxsize',
|
||||
'remember_template',
|
||||
'charset',
|
||||
'compression',
|
||||
'as_separate_files',
|
||||
'knjenc',
|
||||
'xkana',
|
||||
'htmlword_structure_or_data',
|
||||
'htmlword_null',
|
||||
'htmlword_columns',
|
||||
'mediawiki_headers',
|
||||
'mediawiki_structure_or_data',
|
||||
'mediawiki_caption',
|
||||
'pdf_structure_or_data',
|
||||
'odt_structure_or_data',
|
||||
'odt_relation',
|
||||
'odt_comments',
|
||||
'odt_mime',
|
||||
'odt_columns',
|
||||
'odt_null',
|
||||
'codegen_structure_or_data',
|
||||
'codegen_format',
|
||||
'excel_null',
|
||||
'excel_removeCRLF',
|
||||
'excel_columns',
|
||||
'excel_edition',
|
||||
'excel_structure_or_data',
|
||||
'yaml_structure_or_data',
|
||||
'ods_null',
|
||||
'ods_structure_or_data',
|
||||
'ods_columns',
|
||||
'json_structure_or_data',
|
||||
'json_pretty_print',
|
||||
'json_unicode',
|
||||
'xml_structure_or_data',
|
||||
'xml_export_events',
|
||||
'xml_export_functions',
|
||||
'xml_export_procedures',
|
||||
'xml_export_tables',
|
||||
'xml_export_triggers',
|
||||
'xml_export_views',
|
||||
'xml_export_contents',
|
||||
'texytext_structure_or_data',
|
||||
'texytext_columns',
|
||||
'texytext_null',
|
||||
'phparray_structure_or_data',
|
||||
'sql_include_comments',
|
||||
'sql_header_comment',
|
||||
'sql_dates',
|
||||
'sql_relation',
|
||||
'sql_mime',
|
||||
'sql_use_transaction',
|
||||
'sql_disable_fk',
|
||||
'sql_compatibility',
|
||||
'sql_structure_or_data',
|
||||
'sql_create_database',
|
||||
'sql_drop_table',
|
||||
'sql_procedure_function',
|
||||
'sql_create_table',
|
||||
'sql_create_view',
|
||||
'sql_create_trigger',
|
||||
'sql_view_current_user',
|
||||
'sql_simple_view_export',
|
||||
'sql_if_not_exists',
|
||||
'sql_or_replace_view',
|
||||
'sql_auto_increment',
|
||||
'sql_backquotes',
|
||||
'sql_truncate',
|
||||
'sql_delayed',
|
||||
'sql_ignore',
|
||||
'sql_type',
|
||||
'sql_insert_syntax',
|
||||
'sql_max_query_size',
|
||||
'sql_hex_for_binary',
|
||||
'sql_utc_time',
|
||||
'sql_drop_database',
|
||||
'sql_views_as_tables',
|
||||
'sql_metadata',
|
||||
'csv_separator',
|
||||
'csv_enclosed',
|
||||
'csv_escaped',
|
||||
'csv_terminated',
|
||||
'csv_null',
|
||||
'csv_removeCRLF',
|
||||
'csv_columns',
|
||||
'csv_structure_or_data',
|
||||
// csv_replace should have been here but we use it directly from $_POST
|
||||
'latex_caption',
|
||||
'latex_structure_or_data',
|
||||
'latex_structure_caption',
|
||||
'latex_structure_continued_caption',
|
||||
'latex_structure_label',
|
||||
'latex_relation',
|
||||
'latex_comments',
|
||||
'latex_mime',
|
||||
'latex_columns',
|
||||
'latex_data_caption',
|
||||
'latex_data_continued_caption',
|
||||
'latex_data_label',
|
||||
'latex_null',
|
||||
'aliases',
|
||||
];
|
||||
|
||||
foreach ($post_params as $one_post_param) {
|
||||
if (! isset($_POST[$one_post_param])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$GLOBALS[$one_post_param] = $_POST[$one_post_param];
|
||||
}
|
||||
|
||||
Util::checkParameters(['what', 'export_type']);
|
||||
|
||||
// sanitize this parameter which will be used below in a file inclusion
|
||||
$what = Core::securePath($_POST['what']);
|
||||
|
||||
// export class instance, not array of properties, as before
|
||||
/** @var ExportPlugin $export_plugin */
|
||||
$export_plugin = Plugins::getPlugin(
|
||||
'export',
|
||||
$what,
|
||||
'libraries/classes/Plugins/Export/',
|
||||
[
|
||||
'export_type' => $export_type,
|
||||
'single_table' => isset($single_table),
|
||||
]
|
||||
);
|
||||
|
||||
// Check export type
|
||||
if (empty($export_plugin)) {
|
||||
Core::fatalError(__('Bad type!'));
|
||||
}
|
||||
|
||||
/**
|
||||
* valid compression methods
|
||||
*/
|
||||
$compression_methods = [];
|
||||
if ($GLOBALS['cfg']['ZipDump'] && function_exists('gzcompress')) {
|
||||
$compression_methods[] = 'zip';
|
||||
}
|
||||
if ($GLOBALS['cfg']['GZipDump'] && function_exists('gzencode')) {
|
||||
$compression_methods[] = 'gzip';
|
||||
}
|
||||
|
||||
/**
|
||||
* init and variable checking
|
||||
*/
|
||||
$compression = '';
|
||||
$onserver = false;
|
||||
$save_on_server = false;
|
||||
$buffer_needed = false;
|
||||
$back_button = '';
|
||||
$refreshButton = '';
|
||||
$save_filename = '';
|
||||
$file_handle = '';
|
||||
$err_url = '';
|
||||
$filename = '';
|
||||
$separate_files = '';
|
||||
|
||||
// Is it a quick or custom export?
|
||||
if (isset($_POST['quick_or_custom'])
|
||||
&& $_POST['quick_or_custom'] === 'quick'
|
||||
) {
|
||||
$quick_export = true;
|
||||
} else {
|
||||
$quick_export = false;
|
||||
}
|
||||
|
||||
if ($_POST['output_format'] === 'astext') {
|
||||
$asfile = false;
|
||||
} else {
|
||||
$asfile = true;
|
||||
$selectedCompression = $_POST['compression'] ?? '';
|
||||
if (isset($_POST['as_separate_files'])
|
||||
&& ! empty($_POST['as_separate_files'])
|
||||
) {
|
||||
if (! empty($selectedCompression)
|
||||
&& $selectedCompression === 'zip'
|
||||
) {
|
||||
$separate_files = $_POST['as_separate_files'];
|
||||
}
|
||||
}
|
||||
if (in_array($selectedCompression, $compression_methods)) {
|
||||
$compression = $selectedCompression;
|
||||
$buffer_needed = true;
|
||||
}
|
||||
if (($quick_export && ! empty($_POST['quick_export_onserver']))
|
||||
|| (! $quick_export && ! empty($_POST['onserver']))
|
||||
) {
|
||||
if ($quick_export) {
|
||||
$onserver = $_POST['quick_export_onserver'];
|
||||
} else {
|
||||
$onserver = $_POST['onserver'];
|
||||
}
|
||||
// Will we save dump on server?
|
||||
$save_on_server = ! empty($cfg['SaveDir']) && $onserver;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If we are sending the export file (as opposed to just displaying it
|
||||
* as text), we have to bypass the usual PhpMyAdmin\Response mechanism
|
||||
*/
|
||||
if (isset($_POST['output_format']) && $_POST['output_format'] === 'sendit' && ! $save_on_server) {
|
||||
$this->response->disable();
|
||||
//Disable all active buffers (see: ob_get_status(true) at this point)
|
||||
do {
|
||||
if (ob_get_length() > 0 || ob_get_level() > 0) {
|
||||
$hasBuffer = ob_end_clean();
|
||||
} else {
|
||||
$hasBuffer = false;
|
||||
}
|
||||
} while ($hasBuffer);
|
||||
}
|
||||
|
||||
$tables = [];
|
||||
// Generate error url and check for needed variables
|
||||
if ($export_type === 'server') {
|
||||
$err_url = Url::getFromRoute('/server/export');
|
||||
} elseif ($export_type === 'database' && strlen($db) > 0) {
|
||||
$err_url = Url::getFromRoute('/database/export', ['db' => $db]);
|
||||
// Check if we have something to export
|
||||
if (isset($table_select)) {
|
||||
$tables = $table_select;
|
||||
} else {
|
||||
$tables = [];
|
||||
}
|
||||
} elseif ($export_type === 'table' && strlen($db) > 0 && strlen($table) > 0) {
|
||||
$err_url = Url::getFromRoute('/table/export', [
|
||||
'db' => $db,
|
||||
'table' => $table,
|
||||
]);
|
||||
} elseif ($export_type === 'raw') {
|
||||
$err_url = Url::getFromRoute('/server/export', ['sql_query' => $sql_query]);
|
||||
} else {
|
||||
Core::fatalError(__('Bad parameters!'));
|
||||
}
|
||||
|
||||
// Merge SQL Query aliases with Export aliases from
|
||||
// export page, Export page aliases are given more
|
||||
// preference over SQL Query aliases.
|
||||
$parser = new Parser($sql_query);
|
||||
$aliases = [];
|
||||
if (! empty($parser->statements[0])
|
||||
&& ($parser->statements[0] instanceof SelectStatement)
|
||||
) {
|
||||
$aliases = Misc::getAliases($parser->statements[0], $db);
|
||||
}
|
||||
if (! empty($_POST['aliases'])) {
|
||||
$aliases = $this->export->mergeAliases($aliases, $_POST['aliases']);
|
||||
$_SESSION['tmpval']['aliases'] = $_POST['aliases'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Increase time limit for script execution and initializes some variables
|
||||
*/
|
||||
Util::setTimeLimit();
|
||||
if (! empty($cfg['MemoryLimit'])) {
|
||||
ini_set('memory_limit', $cfg['MemoryLimit']);
|
||||
}
|
||||
register_shutdown_function([$this->export, 'shutdown']);
|
||||
// Start with empty buffer
|
||||
$dump_buffer = '';
|
||||
$dump_buffer_len = 0;
|
||||
|
||||
// Array of dump_buffers - used in separate file exports
|
||||
$dump_buffer_objects = [];
|
||||
|
||||
// We send fake headers to avoid browser timeout when buffering
|
||||
$time_start = time();
|
||||
|
||||
// Defines the default <CR><LF> format.
|
||||
// For SQL always use \n as MySQL wants this on all platforms.
|
||||
if ($what === 'sql') {
|
||||
$crlf = "\n";
|
||||
} else {
|
||||
$crlf = PHP_EOL;
|
||||
}
|
||||
|
||||
$output_kanji_conversion = Encoding::canConvertKanji();
|
||||
|
||||
// Do we need to convert charset?
|
||||
$output_charset_conversion = $asfile
|
||||
&& Encoding::isSupported()
|
||||
&& isset($charset) && $charset !== 'utf-8';
|
||||
|
||||
// Use on the fly compression?
|
||||
$GLOBALS['onfly_compression'] = $GLOBALS['cfg']['CompressOnFly']
|
||||
&& $compression === 'gzip';
|
||||
if ($GLOBALS['onfly_compression']) {
|
||||
$GLOBALS['memory_limit'] = $this->export->getMemoryLimit();
|
||||
}
|
||||
|
||||
// Generate filename and mime type if needed
|
||||
if ($asfile) {
|
||||
if (empty($remember_template)) {
|
||||
$remember_template = '';
|
||||
}
|
||||
[$filename, $mime_type] = $this->export->getFilenameAndMimetype(
|
||||
$export_type,
|
||||
$remember_template,
|
||||
$export_plugin,
|
||||
$compression,
|
||||
$filename_template
|
||||
);
|
||||
} else {
|
||||
$mime_type = '';
|
||||
}
|
||||
|
||||
// For raw query export, filename will be export.extension
|
||||
if ($export_type === 'raw') {
|
||||
[$filename] = $this->export->getFinalFilenameAndMimetypeForFilename(
|
||||
$export_plugin,
|
||||
$compression,
|
||||
'export'
|
||||
);
|
||||
}
|
||||
|
||||
// Open file on server if needed
|
||||
if ($save_on_server) {
|
||||
[$save_filename, $message, $file_handle] = $this->export->openFile(
|
||||
$filename,
|
||||
$quick_export
|
||||
);
|
||||
|
||||
// problem opening export file on server?
|
||||
if (! empty($message)) {
|
||||
$this->export->showPage($export_type);
|
||||
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
/**
|
||||
* Send headers depending on whether the user chose to download a dump file
|
||||
* or not
|
||||
*/
|
||||
if ($asfile) {
|
||||
// Download
|
||||
// (avoid rewriting data containing HTML with anchors and forms;
|
||||
// this was reported to happen under Plesk)
|
||||
ini_set('url_rewriter.tags', '');
|
||||
$filename = Sanitize::sanitizeFilename($filename);
|
||||
|
||||
Core::downloadHeader($filename, $mime_type);
|
||||
} else {
|
||||
// HTML
|
||||
if ($export_type === 'database') {
|
||||
$num_tables = count($tables);
|
||||
if ($num_tables === 0) {
|
||||
$message = Message::error(
|
||||
__('No tables found in database.')
|
||||
);
|
||||
$active_page = Url::getFromRoute('/database/export');
|
||||
/** @var DatabaseExportController $controller */
|
||||
$controller = $containerBuilder->get(DatabaseExportController::class);
|
||||
$controller->index();
|
||||
exit;
|
||||
}
|
||||
}
|
||||
[$html, $back_button, $refreshButton] = $this->export->getHtmlForDisplayedExportHeader(
|
||||
$export_type,
|
||||
$db,
|
||||
$table
|
||||
);
|
||||
echo $html;
|
||||
unset($html);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Re - initialize
|
||||
$dump_buffer = '';
|
||||
$dump_buffer_len = 0;
|
||||
|
||||
// Add possibly some comments to export
|
||||
if (! $export_plugin->exportHeader()) {
|
||||
throw new ExportException('Failure during header export.');
|
||||
}
|
||||
|
||||
// Will we need relation & co. setup?
|
||||
$do_relation = isset($GLOBALS[$what . '_relation']);
|
||||
$do_comments = isset($GLOBALS[$what . '_include_comments'])
|
||||
|| isset($GLOBALS[$what . '_comments']);
|
||||
$do_mime = isset($GLOBALS[$what . '_mime']);
|
||||
if ($do_relation || $do_comments || $do_mime) {
|
||||
$this->relation->getRelationsParam();
|
||||
}
|
||||
|
||||
// Include dates in export?
|
||||
$do_dates = isset($GLOBALS[$what . '_dates']);
|
||||
|
||||
$whatStrucOrData = $GLOBALS[$what . '_structure_or_data'];
|
||||
|
||||
if ($export_type === 'raw') {
|
||||
$whatStrucOrData = 'raw';
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the dump
|
||||
*/
|
||||
if ($export_type === 'server') {
|
||||
if (! isset($db_select)) {
|
||||
$db_select = '';
|
||||
}
|
||||
$this->export->exportServer(
|
||||
$db_select,
|
||||
$whatStrucOrData,
|
||||
$export_plugin,
|
||||
$crlf,
|
||||
$err_url,
|
||||
$export_type,
|
||||
$do_relation,
|
||||
$do_comments,
|
||||
$do_mime,
|
||||
$do_dates,
|
||||
$aliases,
|
||||
$separate_files
|
||||
);
|
||||
} elseif ($export_type === 'database') {
|
||||
if (! isset($table_structure) || ! is_array($table_structure)) {
|
||||
$table_structure = [];
|
||||
}
|
||||
if (! isset($table_data) || ! is_array($table_data)) {
|
||||
$table_data = [];
|
||||
}
|
||||
if (! empty($_POST['structure_or_data_forced'])) {
|
||||
$table_structure = $tables;
|
||||
$table_data = $tables;
|
||||
}
|
||||
if (isset($lock_tables)) {
|
||||
$this->export->lockTables($db, $tables, 'READ');
|
||||
try {
|
||||
$this->export->exportDatabase(
|
||||
$db,
|
||||
$tables,
|
||||
$whatStrucOrData,
|
||||
$table_structure,
|
||||
$table_data,
|
||||
$export_plugin,
|
||||
$crlf,
|
||||
$err_url,
|
||||
$export_type,
|
||||
$do_relation,
|
||||
$do_comments,
|
||||
$do_mime,
|
||||
$do_dates,
|
||||
$aliases,
|
||||
$separate_files
|
||||
);
|
||||
} finally {
|
||||
$this->export->unlockTables();
|
||||
}
|
||||
} else {
|
||||
$this->export->exportDatabase(
|
||||
$db,
|
||||
$tables,
|
||||
$whatStrucOrData,
|
||||
$table_structure,
|
||||
$table_data,
|
||||
$export_plugin,
|
||||
$crlf,
|
||||
$err_url,
|
||||
$export_type,
|
||||
$do_relation,
|
||||
$do_comments,
|
||||
$do_mime,
|
||||
$do_dates,
|
||||
$aliases,
|
||||
$separate_files
|
||||
);
|
||||
}
|
||||
} elseif ($export_type === 'raw') {
|
||||
Export::exportRaw(
|
||||
$whatStrucOrData,
|
||||
$export_plugin,
|
||||
$crlf,
|
||||
$err_url,
|
||||
$sql_query,
|
||||
$export_type
|
||||
);
|
||||
} else {
|
||||
// We export just one table
|
||||
// $allrows comes from the form when "Dump all rows" has been selected
|
||||
if (! isset($allrows)) {
|
||||
$allrows = '';
|
||||
}
|
||||
if (! isset($limit_to)) {
|
||||
$limit_to = '0';
|
||||
}
|
||||
if (! isset($limit_from)) {
|
||||
$limit_from = '0';
|
||||
}
|
||||
if (isset($lock_tables)) {
|
||||
try {
|
||||
$this->export->lockTables($db, [$table], 'READ');
|
||||
$this->export->exportTable(
|
||||
$db,
|
||||
$table,
|
||||
$whatStrucOrData,
|
||||
$export_plugin,
|
||||
$crlf,
|
||||
$err_url,
|
||||
$export_type,
|
||||
$do_relation,
|
||||
$do_comments,
|
||||
$do_mime,
|
||||
$do_dates,
|
||||
$allrows,
|
||||
$limit_to,
|
||||
$limit_from,
|
||||
$sql_query,
|
||||
$aliases
|
||||
);
|
||||
} finally {
|
||||
$this->export->unlockTables();
|
||||
}
|
||||
} else {
|
||||
$this->export->exportTable(
|
||||
$db,
|
||||
$table,
|
||||
$whatStrucOrData,
|
||||
$export_plugin,
|
||||
$crlf,
|
||||
$err_url,
|
||||
$export_type,
|
||||
$do_relation,
|
||||
$do_comments,
|
||||
$do_mime,
|
||||
$do_dates,
|
||||
$allrows,
|
||||
$limit_to,
|
||||
$limit_from,
|
||||
$sql_query,
|
||||
$aliases
|
||||
);
|
||||
}
|
||||
}
|
||||
if (! $export_plugin->exportFooter()) {
|
||||
throw new ExportException('Failure during footer export.');
|
||||
}
|
||||
} catch (ExportException $e) {
|
||||
null; // Avoid phpcs error...
|
||||
}
|
||||
|
||||
if ($save_on_server && ! empty($message)) {
|
||||
$this->export->showPage($export_type);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the dump as a file...
|
||||
*/
|
||||
if (empty($asfile)) {
|
||||
echo $this->export->getHtmlForDisplayedExportFooter($back_button, $refreshButton);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert the charset if required.
|
||||
if ($output_charset_conversion) {
|
||||
$dump_buffer = Encoding::convertString(
|
||||
'utf-8',
|
||||
$GLOBALS['charset'],
|
||||
$dump_buffer
|
||||
);
|
||||
}
|
||||
|
||||
// Compression needed?
|
||||
if ($compression) {
|
||||
if (! empty($separate_files)) {
|
||||
$dump_buffer = $this->export->compress(
|
||||
$dump_buffer_objects,
|
||||
$compression,
|
||||
$filename
|
||||
);
|
||||
} else {
|
||||
$dump_buffer = $this->export->compress($dump_buffer, $compression, $filename);
|
||||
}
|
||||
}
|
||||
|
||||
/* If we saved on server, we have to close file now */
|
||||
if ($save_on_server) {
|
||||
$message = $this->export->closeFile(
|
||||
$file_handle,
|
||||
$dump_buffer,
|
||||
$save_filename
|
||||
);
|
||||
$this->export->showPage($export_type);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
echo $dump_buffer;
|
||||
}
|
||||
|
||||
public function checkTimeOut(): void
|
||||
{
|
||||
$this->response->setAjax(true);
|
||||
|
||||
if (isset($_SESSION['pma_export_error'])) {
|
||||
unset($_SESSION['pma_export_error']);
|
||||
$this->response->addJSON('message', 'timeout');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response->addJSON('message', 'success');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,160 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers;
|
||||
|
||||
use PhpMyAdmin\Export\Template as ExportTemplate;
|
||||
use PhpMyAdmin\Export\TemplateModel;
|
||||
use PhpMyAdmin\Relation;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use function is_array;
|
||||
use function is_string;
|
||||
|
||||
final class ExportTemplateController extends AbstractController
|
||||
{
|
||||
/** @var TemplateModel */
|
||||
private $model;
|
||||
|
||||
/** @var Relation */
|
||||
private $relation;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
*/
|
||||
public function __construct(
|
||||
$response,
|
||||
Template $template,
|
||||
TemplateModel $model,
|
||||
Relation $relation
|
||||
) {
|
||||
parent::__construct($response, $template);
|
||||
$this->model = $model;
|
||||
$this->relation = $relation;
|
||||
}
|
||||
|
||||
public function create(): void
|
||||
{
|
||||
global $cfg;
|
||||
|
||||
$cfgRelation = $this->relation->getRelationsParam();
|
||||
|
||||
if (! $cfgRelation['exporttemplateswork']) {
|
||||
return;
|
||||
}
|
||||
|
||||
$template = ExportTemplate::fromArray([
|
||||
'username' => $cfg['Server']['user'],
|
||||
'exportType' => $_POST['exportType'] ?? '',
|
||||
'name' => $_POST['templateName'] ?? '',
|
||||
'data' => $_POST['templateData'] ?? '',
|
||||
]);
|
||||
$result = $this->model->create($cfgRelation['db'], $cfgRelation['export_templates'], $template);
|
||||
|
||||
if (is_string($result)) {
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON('message', $result);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$templates = $this->model->getAll(
|
||||
$cfgRelation['db'],
|
||||
$cfgRelation['export_templates'],
|
||||
$template->getUsername(),
|
||||
$template->getExportType()
|
||||
);
|
||||
|
||||
$this->response->setRequestStatus(true);
|
||||
$this->response->addJSON(
|
||||
'data',
|
||||
$this->template->render('export/template_options', [
|
||||
'templates' => is_array($templates) ? $templates : [],
|
||||
'selected_template' => $_POST['template_id'] ?? null,
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
public function delete(): void
|
||||
{
|
||||
global $cfg;
|
||||
|
||||
$cfgRelation = $this->relation->getRelationsParam();
|
||||
|
||||
if (! $cfgRelation['exporttemplateswork']) {
|
||||
return;
|
||||
}
|
||||
|
||||
$result = $this->model->delete(
|
||||
$cfgRelation['db'],
|
||||
$cfgRelation['export_templates'],
|
||||
$cfg['Server']['user'],
|
||||
(int) $_POST['templateId']
|
||||
);
|
||||
|
||||
if (is_string($result)) {
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON('message', $result);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response->setRequestStatus(true);
|
||||
}
|
||||
|
||||
public function load(): void
|
||||
{
|
||||
global $cfg;
|
||||
|
||||
$cfgRelation = $this->relation->getRelationsParam();
|
||||
|
||||
if (! $cfgRelation['exporttemplateswork']) {
|
||||
return;
|
||||
}
|
||||
|
||||
$template = $this->model->load(
|
||||
$cfgRelation['db'],
|
||||
$cfgRelation['export_templates'],
|
||||
$cfg['Server']['user'],
|
||||
(int) $_POST['templateId']
|
||||
);
|
||||
|
||||
if (! $template instanceof ExportTemplate) {
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON('message', $template);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response->setRequestStatus(true);
|
||||
$this->response->addJSON('data', $template->getData());
|
||||
}
|
||||
|
||||
public function update(): void
|
||||
{
|
||||
global $cfg;
|
||||
|
||||
$cfgRelation = $this->relation->getRelationsParam();
|
||||
|
||||
if (! $cfgRelation['exporttemplateswork']) {
|
||||
return;
|
||||
}
|
||||
|
||||
$template = ExportTemplate::fromArray([
|
||||
'id' => (int) $_POST['templateId'],
|
||||
'username' => $cfg['Server']['user'],
|
||||
'data' => $_POST['templateData'],
|
||||
]);
|
||||
$result = $this->model->update($cfgRelation['db'], $cfgRelation['export_templates'], $template);
|
||||
|
||||
if (is_string($result)) {
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON('message', $result);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response->setRequestStatus(true);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,151 @@
|
|||
<?php
|
||||
/**
|
||||
* Editor for Geometry data types.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers;
|
||||
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\Gis\GisFactory;
|
||||
use PhpMyAdmin\Gis\GisVisualization;
|
||||
use function array_merge;
|
||||
use function in_array;
|
||||
use function intval;
|
||||
use function mb_strpos;
|
||||
use function mb_strtoupper;
|
||||
use function mb_substr;
|
||||
use function substr;
|
||||
use function trim;
|
||||
|
||||
/**
|
||||
* Editor for Geometry data types.
|
||||
*/
|
||||
class GisDataEditorController extends AbstractController
|
||||
{
|
||||
public function index(): void
|
||||
{
|
||||
global $gis_data, $gis_types, $start, $geom_type, $gis_obj, $srid, $wkt, $wkt_with_zero, $PMA_Theme;
|
||||
global $result, $visualizationSettings, $data, $visualization, $open_layers, $geom_count, $dbi;
|
||||
|
||||
if (! isset($_POST['field'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get data if any posted
|
||||
$gis_data = [];
|
||||
if (Core::isValid($_POST['gis_data'], 'array')) {
|
||||
$gis_data = $_POST['gis_data'];
|
||||
}
|
||||
|
||||
$gis_types = [
|
||||
'POINT',
|
||||
'MULTIPOINT',
|
||||
'LINESTRING',
|
||||
'MULTILINESTRING',
|
||||
'POLYGON',
|
||||
'MULTIPOLYGON',
|
||||
'GEOMETRYCOLLECTION',
|
||||
];
|
||||
|
||||
// Extract type from the initial call and make sure that it's a valid one.
|
||||
// Extract from field's values if available, if not use the column type passed.
|
||||
if (! isset($gis_data['gis_type'])) {
|
||||
if (isset($_POST['type']) && $_POST['type'] != '') {
|
||||
$gis_data['gis_type'] = mb_strtoupper($_POST['type']);
|
||||
}
|
||||
if (isset($_POST['value']) && trim($_POST['value']) != '') {
|
||||
$start = substr($_POST['value'], 0, 1) == "'" ? 1 : 0;
|
||||
$gis_data['gis_type'] = mb_substr(
|
||||
$_POST['value'],
|
||||
$start,
|
||||
mb_strpos($_POST['value'], '(') - $start
|
||||
);
|
||||
}
|
||||
if (! isset($gis_data['gis_type'])
|
||||
|| (! in_array($gis_data['gis_type'], $gis_types))
|
||||
) {
|
||||
$gis_data['gis_type'] = $gis_types[0];
|
||||
}
|
||||
}
|
||||
$geom_type = $gis_data['gis_type'];
|
||||
|
||||
// Generate parameters from value passed.
|
||||
$gis_obj = GisFactory::factory($geom_type);
|
||||
if ($gis_obj === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($_POST['value'])) {
|
||||
$gis_data = array_merge(
|
||||
$gis_data,
|
||||
$gis_obj->generateParams($_POST['value'])
|
||||
);
|
||||
}
|
||||
|
||||
// Generate Well Known Text
|
||||
$srid = isset($gis_data['srid']) && $gis_data['srid'] != '' ? $gis_data['srid'] : 0;
|
||||
$wkt = $gis_obj->generateWkt($gis_data, 0);
|
||||
$wkt_with_zero = $gis_obj->generateWkt($gis_data, 0, '0');
|
||||
$result = "'" . $wkt . "'," . $srid;
|
||||
|
||||
// Generate SVG based visualization
|
||||
$visualizationSettings = [
|
||||
'width' => 450,
|
||||
'height' => 300,
|
||||
'spatialColumn' => 'wkt',
|
||||
'mysqlVersion' => $dbi->getVersion(),
|
||||
'isMariaDB' => $dbi->isMariaDB(),
|
||||
];
|
||||
$data = [
|
||||
[
|
||||
'wkt' => $wkt_with_zero,
|
||||
'srid' => $srid,
|
||||
],
|
||||
];
|
||||
$visualization = GisVisualization::getByData($data, $visualizationSettings)
|
||||
->toImage('svg');
|
||||
|
||||
$open_layers = GisVisualization::getByData($data, $visualizationSettings)
|
||||
->asOl();
|
||||
|
||||
// If the call is to update the WKT and visualization make an AJAX response
|
||||
if (isset($_POST['generate']) && $_POST['generate'] == true) {
|
||||
$this->response->addJSON([
|
||||
'result' => $result,
|
||||
'visualization' => $visualization,
|
||||
'openLayers' => $open_layers,
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$geom_count = 1;
|
||||
if ($geom_type === 'GEOMETRYCOLLECTION') {
|
||||
$geom_count = isset($gis_data[$geom_type]['geom_count'])
|
||||
? intval($gis_data[$geom_type]['geom_count']) : 1;
|
||||
if (isset($gis_data[$geom_type]['add_geom'])) {
|
||||
$geom_count++;
|
||||
}
|
||||
}
|
||||
|
||||
$templateOutput = $this->template->render('gis_data_editor_form', [
|
||||
'width' => $visualizationSettings['width'],
|
||||
'height' => $visualizationSettings['height'],
|
||||
'theme_image_path' => $PMA_Theme->getImgPath(),
|
||||
'field' => $_POST['field'],
|
||||
'input_name' => $_POST['input_name'],
|
||||
'srid' => $srid,
|
||||
'visualization' => $visualization,
|
||||
'open_layers' => $open_layers,
|
||||
'gis_types' => $gis_types,
|
||||
'geom_type' => $geom_type,
|
||||
'geom_count' => $geom_count,
|
||||
'gis_data' => $gis_data,
|
||||
'result' => $result,
|
||||
]);
|
||||
|
||||
$this->response->addJSON(['gis_editor' => $templateOutput]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,500 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers;
|
||||
|
||||
use PhpMyAdmin\Charsets;
|
||||
use PhpMyAdmin\Charsets\Charset;
|
||||
use PhpMyAdmin\Charsets\Collation;
|
||||
use PhpMyAdmin\CheckUserPrivileges;
|
||||
use PhpMyAdmin\Config;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Git;
|
||||
use PhpMyAdmin\Html\Generator;
|
||||
use PhpMyAdmin\LanguageManager;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\RecentFavoriteTable;
|
||||
use PhpMyAdmin\Relation;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Server\Select;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\ThemeManager;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\UserPreferences;
|
||||
use PhpMyAdmin\Util;
|
||||
use const E_USER_NOTICE;
|
||||
use const E_USER_WARNING;
|
||||
use const PHP_VERSION;
|
||||
use function count;
|
||||
use function extension_loaded;
|
||||
use function file_exists;
|
||||
use function ini_get;
|
||||
use function preg_match;
|
||||
use function sprintf;
|
||||
use function strlen;
|
||||
use function strtotime;
|
||||
use function trigger_error;
|
||||
|
||||
class HomeController extends AbstractController
|
||||
{
|
||||
/** @var Config */
|
||||
private $config;
|
||||
|
||||
/** @var ThemeManager */
|
||||
private $themeManager;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param Config $config
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $config, ThemeManager $themeManager, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template);
|
||||
$this->config = $config;
|
||||
$this->themeManager = $themeManager;
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $cfg, $server, $collation_connection, $message, $show_query, $db, $table, $err_url;
|
||||
|
||||
if ($this->response->isAjax() && ! empty($_REQUEST['access_time'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This is for $cfg['ShowDatabasesNavigationAsTree'] = false;
|
||||
// See: https://github.com/phpmyadmin/phpmyadmin/issues/16520
|
||||
// The DB is defined here and sent to the JS front-end to refresh the DB tree
|
||||
$db = $_POST['db'] ?? '';
|
||||
$table = '';
|
||||
$show_query = '1';
|
||||
$err_url = Url::getFromRoute('/');
|
||||
|
||||
if ($server > 0 && $this->dbi->isSuperUser()) {
|
||||
$this->dbi->selectDb('mysql');
|
||||
}
|
||||
|
||||
$languageManager = LanguageManager::getInstance();
|
||||
|
||||
if (! empty($message)) {
|
||||
$displayMessage = Generator::getMessage($message);
|
||||
unset($message);
|
||||
}
|
||||
if (isset($_SESSION['partial_logout'])) {
|
||||
$partialLogout = Message::success(__(
|
||||
'You were logged out from one server, to logout completely '
|
||||
. 'from phpMyAdmin, you need to logout from all servers.'
|
||||
))->getDisplay();
|
||||
unset($_SESSION['partial_logout']);
|
||||
}
|
||||
|
||||
$syncFavoriteTables = RecentFavoriteTable::getInstance('favorite')
|
||||
->getHtmlSyncFavoriteTables();
|
||||
|
||||
$hasServer = $server > 0 || count($cfg['Servers']) > 1;
|
||||
if ($hasServer) {
|
||||
$hasServerSelection = $cfg['ServerDefault'] == 0
|
||||
|| (! $cfg['NavigationDisplayServers']
|
||||
&& (count($cfg['Servers']) > 1
|
||||
|| ($server == 0 && count($cfg['Servers']) === 1)));
|
||||
if ($hasServerSelection) {
|
||||
$serverSelection = Select::render(true, true);
|
||||
}
|
||||
|
||||
if ($server > 0) {
|
||||
$checkUserPrivileges = new CheckUserPrivileges($this->dbi);
|
||||
$checkUserPrivileges->getPrivileges();
|
||||
|
||||
$charsets = Charsets::getCharsets($this->dbi, $cfg['Server']['DisableIS']);
|
||||
$collations = Charsets::getCollations($this->dbi, $cfg['Server']['DisableIS']);
|
||||
$charsetsList = [];
|
||||
/** @var Charset $charset */
|
||||
foreach ($charsets as $charset) {
|
||||
$collationsList = [];
|
||||
/** @var Collation $collation */
|
||||
foreach ($collations[$charset->getName()] as $collation) {
|
||||
$collationsList[] = [
|
||||
'name' => $collation->getName(),
|
||||
'description' => $collation->getDescription(),
|
||||
'is_selected' => $collation_connection === $collation->getName(),
|
||||
];
|
||||
}
|
||||
$charsetsList[] = [
|
||||
'name' => $charset->getName(),
|
||||
'description' => $charset->getDescription(),
|
||||
'collations' => $collationsList,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$languageSelector = '';
|
||||
if (empty($cfg['Lang']) && $languageManager->hasChoice()) {
|
||||
$languageSelector = $languageManager->getSelectorDisplay($this->template);
|
||||
}
|
||||
|
||||
$themeSelection = '';
|
||||
if ($cfg['ThemeManager']) {
|
||||
$themeSelection = $this->themeManager->getHtmlSelectBox();
|
||||
}
|
||||
|
||||
$databaseServer = [];
|
||||
if ($server > 0 && $cfg['ShowServerInfo']) {
|
||||
$hostInfo = '';
|
||||
if (! empty($cfg['Server']['verbose'])) {
|
||||
$hostInfo .= $cfg['Server']['verbose'];
|
||||
if ($cfg['ShowServerInfo']) {
|
||||
$hostInfo .= ' (';
|
||||
}
|
||||
}
|
||||
if ($cfg['ShowServerInfo'] || empty($cfg['Server']['verbose'])) {
|
||||
$hostInfo .= $this->dbi->getHostInfo();
|
||||
}
|
||||
if (! empty($cfg['Server']['verbose']) && $cfg['ShowServerInfo']) {
|
||||
$hostInfo .= ')';
|
||||
}
|
||||
|
||||
$serverCharset = Charsets::getServerCharset($this->dbi, $cfg['Server']['DisableIS']);
|
||||
$databaseServer = [
|
||||
'host' => $hostInfo,
|
||||
'type' => Util::getServerType(),
|
||||
'connection' => Generator::getServerSSL(),
|
||||
'version' => $this->dbi->getVersionString() . ' - ' . $this->dbi->getVersionComment(),
|
||||
'protocol' => $this->dbi->getProtoInfo(),
|
||||
'user' => $this->dbi->fetchValue('SELECT USER();'),
|
||||
'charset' => $serverCharset->getDescription() . ' (' . $serverCharset->getName() . ')',
|
||||
];
|
||||
}
|
||||
|
||||
$webServer = [];
|
||||
if ($cfg['ShowServerInfo']) {
|
||||
$webServer['software'] = $_SERVER['SERVER_SOFTWARE'] ?? null;
|
||||
|
||||
if ($server > 0) {
|
||||
$clientVersion = $this->dbi->getClientInfo();
|
||||
if (preg_match('#\d+\.\d+\.\d+#', $clientVersion)) {
|
||||
$clientVersion = 'libmysql - ' . $clientVersion;
|
||||
}
|
||||
|
||||
$webServer['database'] = $clientVersion;
|
||||
$webServer['php_extensions'] = Util::listPHPExtensions();
|
||||
$webServer['php_version'] = PHP_VERSION;
|
||||
}
|
||||
}
|
||||
|
||||
$relation = new Relation($this->dbi);
|
||||
if ($server > 0) {
|
||||
$cfgRelation = $relation->getRelationsParam();
|
||||
if (! $cfgRelation['allworks']
|
||||
&& $cfg['PmaNoRelation_DisableWarning'] == false
|
||||
) {
|
||||
$messageText = __(
|
||||
'The phpMyAdmin configuration storage is not completely '
|
||||
. 'configured, some extended features have been deactivated. '
|
||||
. '%sFind out why%s. '
|
||||
);
|
||||
if ($cfg['ZeroConf'] == true) {
|
||||
$messageText .= '<br>' .
|
||||
__(
|
||||
'Or alternately go to \'Operations\' tab of any database '
|
||||
. 'to set it up there.'
|
||||
);
|
||||
}
|
||||
$messageInstance = Message::notice($messageText);
|
||||
$messageInstance->addParamHtml(
|
||||
'<a href="' . Url::getFromRoute('/check-relations')
|
||||
. '" data-post="' . Url::getCommon() . '">'
|
||||
);
|
||||
$messageInstance->addParamHtml('</a>');
|
||||
/* Show error if user has configured something, notice elsewhere */
|
||||
if (! empty($cfg['Servers'][$server]['pmadb'])) {
|
||||
$messageInstance->isError(true);
|
||||
}
|
||||
$configStorageMessage = $messageInstance->getDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
$this->checkRequirements();
|
||||
|
||||
$git = new Git($this->config);
|
||||
|
||||
$this->render('home/index', [
|
||||
'message' => $displayMessage ?? '',
|
||||
'partial_logout' => $partialLogout ?? '',
|
||||
'is_git_revision' => $git->isGitRevision(),
|
||||
'server' => $server,
|
||||
'sync_favorite_tables' => $syncFavoriteTables,
|
||||
'has_server' => $hasServer,
|
||||
'is_demo' => $cfg['DBG']['demo'],
|
||||
'has_server_selection' => $hasServerSelection ?? false,
|
||||
'server_selection' => $serverSelection ?? '',
|
||||
'has_change_password_link' => $cfg['Server']['auth_type'] !== 'config' && $cfg['ShowChgPassword'],
|
||||
'charsets' => $charsetsList ?? [],
|
||||
'language_selector' => $languageSelector,
|
||||
'theme_selection' => $themeSelection,
|
||||
'database_server' => $databaseServer,
|
||||
'web_server' => $webServer,
|
||||
'show_php_info' => $cfg['ShowPhpInfo'],
|
||||
'is_version_checked' => $cfg['VersionCheck'],
|
||||
'phpmyadmin_version' => PMA_VERSION,
|
||||
'config_storage_message' => $configStorageMessage ?? '',
|
||||
]);
|
||||
}
|
||||
|
||||
public function setTheme(): void
|
||||
{
|
||||
$this->themeManager->setActiveTheme($_POST['set_theme']);
|
||||
$this->themeManager->setThemeCookie();
|
||||
|
||||
$userPreferences = new UserPreferences();
|
||||
$preferences = $userPreferences->load();
|
||||
$preferences['config_data']['ThemeDefault'] = $_POST['set_theme'];
|
||||
$userPreferences->save($preferences['config_data']);
|
||||
|
||||
$this->response->header('Location: index.php?route=/' . Url::getCommonRaw([], '&'));
|
||||
}
|
||||
|
||||
public function setCollationConnection(): void
|
||||
{
|
||||
$this->config->setUserValue(
|
||||
null,
|
||||
'DefaultConnectionCollation',
|
||||
$_POST['collation_connection'],
|
||||
'utf8mb4_unicode_ci'
|
||||
);
|
||||
|
||||
$this->response->header('Location: index.php?route=/' . Url::getCommonRaw([], '&'));
|
||||
}
|
||||
|
||||
public function reloadRecentTablesList(): void
|
||||
{
|
||||
if (! $this->response->isAjax()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response->addJSON([
|
||||
'list' => RecentFavoriteTable::getInstance('recent')->getHtmlList(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function gitRevision(): void
|
||||
{
|
||||
if (! $this->response->isAjax()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$git = new Git($this->config);
|
||||
|
||||
if (! $git->isGitRevision()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$commit = $git->checkGitRevision();
|
||||
|
||||
if (! $this->config->get('PMA_VERSION_GIT') || $commit === null) {
|
||||
$this->response->setRequestStatus(false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$commit['author']['date'] = Util::localisedDate(strtotime($commit['author']['date']));
|
||||
$commit['committer']['date'] = Util::localisedDate(strtotime($commit['committer']['date']));
|
||||
|
||||
$this->render('home/git_info', $commit);
|
||||
}
|
||||
|
||||
private function checkRequirements(): void
|
||||
{
|
||||
global $cfg, $server, $lang;
|
||||
|
||||
/**
|
||||
* mbstring is used for handling multibytes inside parser, so it is good
|
||||
* to tell user something might be broken without it, see bug #1063149.
|
||||
*/
|
||||
if (! extension_loaded('mbstring')) {
|
||||
trigger_error(
|
||||
__(
|
||||
'The mbstring PHP extension was not found and you seem to be using'
|
||||
. ' a multibyte charset. Without the mbstring extension phpMyAdmin'
|
||||
. ' is unable to split strings correctly and it may result in'
|
||||
. ' unexpected results.'
|
||||
),
|
||||
E_USER_WARNING
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Missing functionality
|
||||
*/
|
||||
if (! extension_loaded('curl') && ! ini_get('allow_url_fopen')) {
|
||||
trigger_error(
|
||||
__(
|
||||
'The curl extension was not found and allow_url_fopen is '
|
||||
. 'disabled. Due to this some features such as error reporting '
|
||||
. 'or version check are disabled.'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ($cfg['LoginCookieValidityDisableWarning'] == false) {
|
||||
/**
|
||||
* Check whether session.gc_maxlifetime limits session validity.
|
||||
*/
|
||||
$gc_time = (int) ini_get('session.gc_maxlifetime');
|
||||
if ($gc_time < $cfg['LoginCookieValidity']) {
|
||||
trigger_error(
|
||||
__(
|
||||
'Your PHP parameter [a@https://www.php.net/manual/en/session.' .
|
||||
'configuration.php#ini.session.gc-maxlifetime@_blank]session.' .
|
||||
'gc_maxlifetime[/a] is lower than cookie validity configured ' .
|
||||
'in phpMyAdmin, because of this, your login might expire sooner ' .
|
||||
'than configured in phpMyAdmin.'
|
||||
),
|
||||
E_USER_WARNING
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether LoginCookieValidity is limited by LoginCookieStore.
|
||||
*/
|
||||
if ($cfg['LoginCookieStore'] != 0
|
||||
&& $cfg['LoginCookieStore'] < $cfg['LoginCookieValidity']
|
||||
) {
|
||||
trigger_error(
|
||||
__(
|
||||
'Login cookie store is lower than cookie validity configured in ' .
|
||||
'phpMyAdmin, because of this, your login will expire sooner than ' .
|
||||
'configured in phpMyAdmin.'
|
||||
),
|
||||
E_USER_WARNING
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Warning if using the default MySQL controluser account
|
||||
*/
|
||||
if (isset($cfg['Server']['controluser'], $cfg['Server']['controlpass'])
|
||||
&& $server != 0
|
||||
&& $cfg['Server']['controluser'] === 'pma'
|
||||
&& $cfg['Server']['controlpass'] === 'pmapass'
|
||||
) {
|
||||
trigger_error(
|
||||
__(
|
||||
'Your server is running with default values for the ' .
|
||||
'controluser and password (controlpass) and is open to ' .
|
||||
'intrusion; you really should fix this security weakness' .
|
||||
' by changing the password for controluser \'pma\'.'
|
||||
),
|
||||
E_USER_WARNING
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user does not have defined blowfish secret and it is being used.
|
||||
*/
|
||||
if (! empty($_SESSION['encryption_key'])) {
|
||||
if (empty($cfg['blowfish_secret'])) {
|
||||
trigger_error(
|
||||
__(
|
||||
'The configuration file now needs a secret passphrase (blowfish_secret).'
|
||||
),
|
||||
E_USER_WARNING
|
||||
);
|
||||
} elseif (strlen($cfg['blowfish_secret']) < 32) {
|
||||
trigger_error(
|
||||
__(
|
||||
'The secret passphrase in configuration (blowfish_secret) is too short.'
|
||||
),
|
||||
E_USER_WARNING
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for existence of config directory which should not exist in
|
||||
* production environment.
|
||||
*/
|
||||
if (@file_exists(ROOT_PATH . 'config')) {
|
||||
trigger_error(
|
||||
__(
|
||||
'Directory [code]config[/code], which is used by the setup script, ' .
|
||||
'still exists in your phpMyAdmin directory. It is strongly ' .
|
||||
'recommended to remove it once phpMyAdmin has been configured. ' .
|
||||
'Otherwise the security of your server may be compromised by ' .
|
||||
'unauthorized people downloading your configuration.'
|
||||
),
|
||||
E_USER_WARNING
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Warning about Suhosin only if its simulation mode is not enabled
|
||||
*/
|
||||
if ($cfg['SuhosinDisableWarning'] == false
|
||||
&& ini_get('suhosin.request.max_value_length')
|
||||
&& ini_get('suhosin.simulation') == '0'
|
||||
) {
|
||||
trigger_error(
|
||||
sprintf(
|
||||
__(
|
||||
'Server running with Suhosin. Please refer ' .
|
||||
'to %sdocumentation%s for possible issues.'
|
||||
),
|
||||
'[doc@faq1-38]',
|
||||
'[/doc]'
|
||||
),
|
||||
E_USER_WARNING
|
||||
);
|
||||
}
|
||||
|
||||
/* Missing template cache */
|
||||
if ($this->config->getTempDir('twig') === null) {
|
||||
trigger_error(
|
||||
sprintf(
|
||||
__(
|
||||
'The $cfg[\'TempDir\'] (%s) is not accessible. ' .
|
||||
'phpMyAdmin is not able to cache templates and will ' .
|
||||
'be slow because of this.'
|
||||
),
|
||||
$this->config->get('TempDir')
|
||||
),
|
||||
E_USER_WARNING
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Warning about incomplete translations.
|
||||
*
|
||||
* The data file is created while creating release by ./scripts/remove-incomplete-mo
|
||||
*/
|
||||
if (! @file_exists(ROOT_PATH . 'libraries/language_stats.inc.php')) {
|
||||
return;
|
||||
}
|
||||
|
||||
include ROOT_PATH . 'libraries/language_stats.inc.php';
|
||||
/*
|
||||
* This message is intentionally not translated, because we're
|
||||
* handling incomplete translations here and focus on english
|
||||
* speaking users.
|
||||
*/
|
||||
if (! isset($GLOBALS['language_stats'][$lang])
|
||||
|| $GLOBALS['language_stats'][$lang] >= $cfg['TranslationWarningThreshold']
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
trigger_error(
|
||||
'You are using an incomplete translation, please help to make it '
|
||||
. 'better by [a@https://www.phpmyadmin.net/translate/'
|
||||
. '@_blank]contributing[/a].',
|
||||
E_USER_NOTICE
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,884 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers;
|
||||
|
||||
use PhpMyAdmin\Bookmark;
|
||||
use PhpMyAdmin\Console;
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Encoding;
|
||||
use PhpMyAdmin\File;
|
||||
use PhpMyAdmin\Html\Generator;
|
||||
use PhpMyAdmin\Import;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\ParseAnalyze;
|
||||
use PhpMyAdmin\Plugins;
|
||||
use PhpMyAdmin\Plugins\ImportPlugin;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Sql;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use Throwable;
|
||||
use function define;
|
||||
use function htmlspecialchars;
|
||||
use function in_array;
|
||||
use function ini_get;
|
||||
use function ini_set;
|
||||
use function intval;
|
||||
use function is_array;
|
||||
use function is_link;
|
||||
use function is_uploaded_file;
|
||||
use function mb_strlen;
|
||||
use function mb_strtolower;
|
||||
use function preg_match;
|
||||
use function preg_quote;
|
||||
use function preg_replace;
|
||||
use function sprintf;
|
||||
use function strlen;
|
||||
use function substr;
|
||||
use function time;
|
||||
use function trim;
|
||||
|
||||
final class ImportController extends AbstractController
|
||||
{
|
||||
/** @var Import */
|
||||
private $import;
|
||||
|
||||
/** @var Sql */
|
||||
private $sql;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, Import $import, Sql $sql, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template);
|
||||
$this->import = $import;
|
||||
$this->sql = $sql;
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $cfg, $collation_connection, $db, $import_type, $table, $goto, $display_query, $PMA_Theme;
|
||||
global $format, $local_import_file, $ajax_reload, $import_text, $sql_query, $message, $err_url, $url_params;
|
||||
global $memory_limit, $read_limit, $finished, $offset, $charset_conversion, $charset_of_file;
|
||||
global $timestamp, $maximum_time, $timeout_passed, $import_file, $go_sql, $sql_file, $error, $max_sql_len, $msg;
|
||||
global $sql_query_disabled, $executed_queries, $run_query, $reset_charset, $bookmark_created;
|
||||
global $result, $import_file_name, $sql_data, $import_notice, $read_multiply, $my_die, $active_page;
|
||||
global $show_as_php, $reload, $charset_connection, $is_js_confirmed, $MAX_FILE_SIZE, $message_to_show;
|
||||
global $noplugin, $skip_queries;
|
||||
|
||||
$charset_of_file = $_POST['charset_of_file'] ?? null;
|
||||
$format = $_POST['format'] ?? '';
|
||||
$import_type = $_POST['import_type'] ?? null;
|
||||
$is_js_confirmed = $_POST['is_js_confirmed'] ?? null;
|
||||
$MAX_FILE_SIZE = $_POST['MAX_FILE_SIZE'] ?? null;
|
||||
$message_to_show = $_POST['message_to_show'] ?? null;
|
||||
$noplugin = $_POST['noplugin'] ?? null;
|
||||
$skip_queries = $_POST['skip_queries'] ?? null;
|
||||
$local_import_file = $_POST['local_import_file'] ?? null;
|
||||
$show_as_php = $_POST['show_as_php'] ?? null;
|
||||
|
||||
/* Enable LOAD DATA LOCAL INFILE for LDI plugin */
|
||||
if ($format === 'ldi') {
|
||||
define('PMA_ENABLE_LDI', 1);
|
||||
}
|
||||
|
||||
// If there is a request to 'Simulate DML'.
|
||||
if (isset($_POST['simulate_dml'])) {
|
||||
$this->import->handleSimulateDmlRequest();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// If it's a refresh console bookmarks request
|
||||
if (isset($_GET['console_bookmark_refresh'])) {
|
||||
$this->response->addJSON(
|
||||
'console_message_bookmark',
|
||||
Console::getBookmarkContent()
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
// If it's a console bookmark add request
|
||||
if (isset($_POST['console_bookmark_add'])) {
|
||||
if (! isset($_POST['label'], $_POST['db'], $_POST['bookmark_query'], $_POST['shared'])) {
|
||||
$this->response->addJSON('message', __('Incomplete params'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$cfgBookmark = Bookmark::getParams($cfg['Server']['user']);
|
||||
|
||||
if (! is_array($cfgBookmark)) {
|
||||
$cfgBookmark = [];
|
||||
}
|
||||
|
||||
$bookmarkFields = [
|
||||
'bkm_database' => $_POST['db'],
|
||||
'bkm_user' => $cfgBookmark['user'],
|
||||
'bkm_sql_query' => $_POST['bookmark_query'],
|
||||
'bkm_label' => $_POST['label'],
|
||||
];
|
||||
$isShared = ($_POST['shared'] === 'true');
|
||||
$bookmark = Bookmark::createBookmark(
|
||||
$this->dbi,
|
||||
$cfg['Server']['user'],
|
||||
$bookmarkFields,
|
||||
$isShared
|
||||
);
|
||||
if ($bookmark !== false && $bookmark->save()) {
|
||||
$this->response->addJSON('message', __('Succeeded'));
|
||||
$this->response->addJSON('data', $bookmarkFields);
|
||||
$this->response->addJSON('isShared', $isShared);
|
||||
} else {
|
||||
$this->response->addJSON('message', __('Failed'));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// reset import messages for ajax request
|
||||
$_SESSION['Import_message']['message'] = null;
|
||||
$_SESSION['Import_message']['go_back_url'] = null;
|
||||
// default values
|
||||
$reload = false;
|
||||
|
||||
// Use to identify current cycle is executing
|
||||
// a multiquery statement or stored routine
|
||||
if (! isset($_SESSION['is_multi_query'])) {
|
||||
$_SESSION['is_multi_query'] = false;
|
||||
}
|
||||
|
||||
$ajax_reload = [];
|
||||
$import_text = '';
|
||||
// Are we just executing plain query or sql file?
|
||||
// (eg. non import, but query box/window run)
|
||||
if (! empty($sql_query)) {
|
||||
// apply values for parameters
|
||||
if (! empty($_POST['parameterized'])
|
||||
&& ! empty($_POST['parameters'])
|
||||
&& is_array($_POST['parameters'])
|
||||
) {
|
||||
$parameters = $_POST['parameters'];
|
||||
foreach ($parameters as $parameter => $replacement) {
|
||||
$quoted = preg_quote($parameter, '/');
|
||||
// making sure that :param does not apply values to :param1
|
||||
$sql_query = preg_replace(
|
||||
'/' . $quoted . '([^a-zA-Z0-9_])/',
|
||||
$this->dbi->escapeString($replacement) . '${1}',
|
||||
$sql_query
|
||||
);
|
||||
// for parameters the appear at the end of the string
|
||||
$sql_query = preg_replace(
|
||||
'/' . $quoted . '$/',
|
||||
$this->dbi->escapeString($replacement),
|
||||
$sql_query
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// run SQL query
|
||||
$import_text = $sql_query;
|
||||
$import_type = 'query';
|
||||
$format = 'sql';
|
||||
$_SESSION['sql_from_query_box'] = true;
|
||||
|
||||
// If there is a request to ROLLBACK when finished.
|
||||
if (isset($_POST['rollback_query'])) {
|
||||
$this->import->handleRollbackRequest($import_text);
|
||||
}
|
||||
|
||||
// refresh navigation and main panels
|
||||
if (preg_match('/^(DROP)\s+(VIEW|TABLE|DATABASE|SCHEMA)\s+/i', $sql_query)) {
|
||||
$reload = true;
|
||||
$ajax_reload['reload'] = true;
|
||||
}
|
||||
|
||||
// refresh navigation panel only
|
||||
if (preg_match(
|
||||
'/^(CREATE|ALTER)\s+(VIEW|TABLE|DATABASE|SCHEMA)\s+/i',
|
||||
$sql_query
|
||||
)) {
|
||||
$ajax_reload['reload'] = true;
|
||||
}
|
||||
|
||||
// do a dynamic reload if table is RENAMED
|
||||
// (by sending the instruction to the AJAX response handler)
|
||||
if (preg_match(
|
||||
'/^RENAME\s+TABLE\s+(.*?)\s+TO\s+(.*?)($|;|\s)/i',
|
||||
$sql_query,
|
||||
$rename_table_names
|
||||
)) {
|
||||
$ajax_reload['reload'] = true;
|
||||
$ajax_reload['table_name'] = Util::unQuote(
|
||||
$rename_table_names[2]
|
||||
);
|
||||
}
|
||||
|
||||
$sql_query = '';
|
||||
} elseif (! empty($sql_file)) {
|
||||
// run uploaded SQL file
|
||||
$import_file = $sql_file;
|
||||
$import_type = 'queryfile';
|
||||
$format = 'sql';
|
||||
unset($sql_file);
|
||||
} elseif (! empty($_POST['id_bookmark'])) {
|
||||
// run bookmark
|
||||
$import_type = 'query';
|
||||
$format = 'sql';
|
||||
}
|
||||
|
||||
// If we didn't get any parameters, either user called this directly, or
|
||||
// upload limit has been reached, let's assume the second possibility.
|
||||
if ($_POST == [] && $_GET == []) {
|
||||
$message = Message::error(
|
||||
__(
|
||||
'You probably tried to upload a file that is too large. Please refer ' .
|
||||
'to %sdocumentation%s for a workaround for this limit.'
|
||||
)
|
||||
);
|
||||
$message->addParam('[doc@faq1-16]');
|
||||
$message->addParam('[/doc]');
|
||||
|
||||
// so we can obtain the message
|
||||
$_SESSION['Import_message']['message'] = $message->getDisplay();
|
||||
$_SESSION['Import_message']['go_back_url'] = $goto;
|
||||
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON('message', $message);
|
||||
|
||||
return; // the footer is displayed automatically
|
||||
}
|
||||
|
||||
// Add console message id to response output
|
||||
if (isset($_POST['console_message_id'])) {
|
||||
$this->response->addJSON('console_message_id', $_POST['console_message_id']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets globals from $_POST patterns, for import plugins
|
||||
* We only need to load the selected plugin
|
||||
*/
|
||||
|
||||
if (! in_array(
|
||||
$format,
|
||||
[
|
||||
'csv',
|
||||
'ldi',
|
||||
'mediawiki',
|
||||
'ods',
|
||||
'shp',
|
||||
'sql',
|
||||
'xml',
|
||||
]
|
||||
)
|
||||
) {
|
||||
// this should not happen for a normal user
|
||||
// but only during an attack
|
||||
Core::fatalError('Incorrect format parameter');
|
||||
}
|
||||
|
||||
$post_patterns = [
|
||||
'/^force_file_/',
|
||||
'/^' . $format . '_/',
|
||||
];
|
||||
|
||||
Core::setPostAsGlobal($post_patterns);
|
||||
|
||||
// Check needed parameters
|
||||
Util::checkParameters(['import_type', 'format']);
|
||||
|
||||
// We don't want anything special in format
|
||||
$format = Core::securePath($format);
|
||||
|
||||
if (strlen($table) > 0 && strlen($db) > 0) {
|
||||
$url_params = [
|
||||
'db' => $db,
|
||||
'table' => $table,
|
||||
];
|
||||
} elseif (strlen($db) > 0) {
|
||||
$url_params = ['db' => $db];
|
||||
} else {
|
||||
$url_params = [];
|
||||
}
|
||||
|
||||
// Create error and goto url
|
||||
if ($import_type === 'table') {
|
||||
$goto = Url::getFromRoute('/table/import');
|
||||
} elseif ($import_type === 'database') {
|
||||
$goto = Url::getFromRoute('/database/import');
|
||||
} elseif ($import_type === 'server') {
|
||||
$goto = Url::getFromRoute('/server/import');
|
||||
} elseif (empty($goto) || ! preg_match('@^index\.php$@i', $goto)) {
|
||||
if (strlen($table) > 0 && strlen($db) > 0) {
|
||||
$goto = Url::getFromRoute('/table/structure');
|
||||
} elseif (strlen($db) > 0) {
|
||||
$goto = Url::getFromRoute('/database/structure');
|
||||
} else {
|
||||
$goto = Url::getFromRoute('/server/sql');
|
||||
}
|
||||
}
|
||||
$err_url = $goto . Url::getCommon($url_params, '&');
|
||||
$_SESSION['Import_message']['go_back_url'] = $err_url;
|
||||
|
||||
if (strlen($db) > 0) {
|
||||
$this->dbi->selectDb($db);
|
||||
}
|
||||
|
||||
Util::setTimeLimit();
|
||||
if (! empty($cfg['MemoryLimit'])) {
|
||||
ini_set('memory_limit', $cfg['MemoryLimit']);
|
||||
}
|
||||
|
||||
$timestamp = time();
|
||||
if (isset($_POST['allow_interrupt'])) {
|
||||
$maximum_time = ini_get('max_execution_time');
|
||||
} else {
|
||||
$maximum_time = 0;
|
||||
}
|
||||
|
||||
// set default values
|
||||
$timeout_passed = false;
|
||||
$error = false;
|
||||
$read_multiply = 1;
|
||||
$finished = false;
|
||||
$offset = 0;
|
||||
$max_sql_len = 0;
|
||||
$sql_query = '';
|
||||
$sql_query_disabled = false;
|
||||
$go_sql = false;
|
||||
$executed_queries = 0;
|
||||
$run_query = true;
|
||||
$charset_conversion = false;
|
||||
$reset_charset = false;
|
||||
$bookmark_created = false;
|
||||
$msg = 'Sorry an unexpected error happened!';
|
||||
|
||||
/** @var mixed|bool $result */
|
||||
$result = false;
|
||||
|
||||
// Bookmark Support: get a query back from bookmark if required
|
||||
if (! empty($_POST['id_bookmark'])) {
|
||||
$id_bookmark = (int) $_POST['id_bookmark'];
|
||||
switch ($_POST['action_bookmark']) {
|
||||
case 0: // bookmarked query that have to be run
|
||||
$bookmark = Bookmark::get(
|
||||
$this->dbi,
|
||||
$cfg['Server']['user'],
|
||||
$db,
|
||||
$id_bookmark,
|
||||
'id',
|
||||
isset($_POST['action_bookmark_all'])
|
||||
);
|
||||
if (! $bookmark instanceof Bookmark) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (! empty($_POST['bookmark_variable'])) {
|
||||
$import_text = $bookmark->applyVariables(
|
||||
$_POST['bookmark_variable']
|
||||
);
|
||||
} else {
|
||||
$import_text = $bookmark->getQuery();
|
||||
}
|
||||
|
||||
// refresh navigation and main panels
|
||||
if (preg_match(
|
||||
'/^(DROP)\s+(VIEW|TABLE|DATABASE|SCHEMA)\s+/i',
|
||||
$import_text
|
||||
)) {
|
||||
$reload = true;
|
||||
$ajax_reload['reload'] = true;
|
||||
}
|
||||
|
||||
// refresh navigation panel only
|
||||
if (preg_match(
|
||||
'/^(CREATE|ALTER)\s+(VIEW|TABLE|DATABASE|SCHEMA)\s+/i',
|
||||
$import_text
|
||||
)
|
||||
) {
|
||||
$ajax_reload['reload'] = true;
|
||||
}
|
||||
break;
|
||||
case 1: // bookmarked query that have to be displayed
|
||||
$bookmark = Bookmark::get(
|
||||
$this->dbi,
|
||||
$cfg['Server']['user'],
|
||||
$db,
|
||||
$id_bookmark
|
||||
);
|
||||
if (! $bookmark instanceof Bookmark) {
|
||||
break;
|
||||
}
|
||||
$import_text = $bookmark->getQuery();
|
||||
if ($this->response->isAjax()) {
|
||||
$message = Message::success(__('Showing bookmark'));
|
||||
$this->response->setRequestStatus($message->isSuccess());
|
||||
$this->response->addJSON('message', $message);
|
||||
$this->response->addJSON('sql_query', $import_text);
|
||||
$this->response->addJSON('action_bookmark', $_POST['action_bookmark']);
|
||||
|
||||
return;
|
||||
} else {
|
||||
$run_query = false;
|
||||
}
|
||||
break;
|
||||
case 2: // bookmarked query that have to be deleted
|
||||
$bookmark = Bookmark::get(
|
||||
$this->dbi,
|
||||
$cfg['Server']['user'],
|
||||
$db,
|
||||
$id_bookmark
|
||||
);
|
||||
if (! $bookmark instanceof Bookmark) {
|
||||
break;
|
||||
}
|
||||
$bookmark->delete();
|
||||
if ($this->response->isAjax()) {
|
||||
$message = Message::success(
|
||||
__('The bookmark has been deleted.')
|
||||
);
|
||||
$this->response->setRequestStatus($message->isSuccess());
|
||||
$this->response->addJSON('message', $message);
|
||||
$this->response->addJSON('action_bookmark', $_POST['action_bookmark']);
|
||||
$this->response->addJSON('id_bookmark', $id_bookmark);
|
||||
|
||||
return;
|
||||
} else {
|
||||
$run_query = false;
|
||||
$error = true; // this is kind of hack to skip processing the query
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Do no run query if we show PHP code
|
||||
if (isset($show_as_php)) {
|
||||
$run_query = false;
|
||||
$go_sql = true;
|
||||
}
|
||||
|
||||
// We can not read all at once, otherwise we can run out of memory
|
||||
$memory_limit = trim((string) ini_get('memory_limit'));
|
||||
// 2 MB as default
|
||||
if (empty($memory_limit)) {
|
||||
$memory_limit = 2 * 1024 * 1024;
|
||||
}
|
||||
// In case no memory limit we work on 10MB chunks
|
||||
if ($memory_limit == -1) {
|
||||
$memory_limit = 10 * 1024 * 1024;
|
||||
}
|
||||
|
||||
// Calculate value of the limit
|
||||
$memoryUnit = mb_strtolower(substr((string) $memory_limit, -1));
|
||||
if ($memoryUnit === 'm') {
|
||||
$memory_limit = (int) substr((string) $memory_limit, 0, -1) * 1024 * 1024;
|
||||
} elseif ($memoryUnit === 'k') {
|
||||
$memory_limit = (int) substr((string) $memory_limit, 0, -1) * 1024;
|
||||
} elseif ($memoryUnit === 'g') {
|
||||
$memory_limit = (int) substr((string) $memory_limit, 0, -1) * 1024 * 1024 * 1024;
|
||||
} else {
|
||||
$memory_limit = (int) $memory_limit;
|
||||
}
|
||||
|
||||
// Just to be sure, there might be lot of memory needed for uncompression
|
||||
$read_limit = $memory_limit / 8;
|
||||
|
||||
// handle filenames
|
||||
if (isset($_FILES['import_file'])) {
|
||||
$import_file = $_FILES['import_file']['tmp_name'];
|
||||
$import_file_name = $_FILES['import_file']['name'];
|
||||
}
|
||||
if (! empty($local_import_file) && ! empty($cfg['UploadDir'])) {
|
||||
// sanitize $local_import_file as it comes from a POST
|
||||
$local_import_file = Core::securePath($local_import_file);
|
||||
|
||||
$import_file = Util::userDir($cfg['UploadDir'])
|
||||
. $local_import_file;
|
||||
|
||||
/*
|
||||
* Do not allow symlinks to avoid security issues
|
||||
* (user can create symlink to file they can not access,
|
||||
* but phpMyAdmin can).
|
||||
*/
|
||||
if (@is_link($import_file)) {
|
||||
$import_file = 'none';
|
||||
}
|
||||
} elseif (empty($import_file) || ! is_uploaded_file($import_file)) {
|
||||
$import_file = 'none';
|
||||
}
|
||||
|
||||
// Do we have file to import?
|
||||
|
||||
if ($import_file !== 'none' && ! $error) {
|
||||
/**
|
||||
* Handle file compression
|
||||
*/
|
||||
$importHandle = new File($import_file);
|
||||
$importHandle->checkUploadedFile();
|
||||
if ($importHandle->isError()) {
|
||||
/** @var Message $errorMessage */
|
||||
$errorMessage = $importHandle->getError();
|
||||
|
||||
$importHandle->close();
|
||||
|
||||
$_SESSION['Import_message']['message'] = $errorMessage->getDisplay();
|
||||
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON('message', $errorMessage->getDisplay());
|
||||
$this->response->addHTML($errorMessage->getDisplay());
|
||||
|
||||
return;
|
||||
}
|
||||
$importHandle->setDecompressContent(true);
|
||||
$importHandle->open();
|
||||
if ($importHandle->isError()) {
|
||||
/** @var Message $errorMessage */
|
||||
$errorMessage = $importHandle->getError();
|
||||
|
||||
$importHandle->close();
|
||||
|
||||
$_SESSION['Import_message']['message'] = $errorMessage->getDisplay();
|
||||
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON('message', $errorMessage->getDisplay());
|
||||
$this->response->addHTML($errorMessage->getDisplay());
|
||||
|
||||
return;
|
||||
}
|
||||
} elseif (! $error && (! isset($import_text) || empty($import_text))) {
|
||||
$message = Message::error(
|
||||
__(
|
||||
'No data was received to import. Either no file name was ' .
|
||||
'submitted, or the file size exceeded the maximum size permitted ' .
|
||||
'by your PHP configuration. See [doc@faq1-16]FAQ 1.16[/doc].'
|
||||
)
|
||||
);
|
||||
|
||||
$_SESSION['Import_message']['message'] = $message->getDisplay();
|
||||
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON('message', $message->getDisplay());
|
||||
$this->response->addHTML($message->getDisplay());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert the file's charset if necessary
|
||||
if (Encoding::isSupported() && isset($charset_of_file)) {
|
||||
if ($charset_of_file !== 'utf-8') {
|
||||
$charset_conversion = true;
|
||||
}
|
||||
} elseif (isset($charset_of_file) && $charset_of_file !== 'utf-8') {
|
||||
$this->dbi->query('SET NAMES \'' . $charset_of_file . '\'');
|
||||
// We can not show query in this case, it is in different charset
|
||||
$sql_query_disabled = true;
|
||||
$reset_charset = true;
|
||||
}
|
||||
|
||||
// Something to skip? (because timeout has passed)
|
||||
if (! $error && isset($_POST['skip'])) {
|
||||
$original_skip = $skip = intval($_POST['skip']);
|
||||
while ($skip > 0 && ! $finished) {
|
||||
$this->import->getNextChunk($importHandle ?? null, $skip < $read_limit ? $skip : $read_limit);
|
||||
// Disable read progressivity, otherwise we eat all memory!
|
||||
$read_multiply = 1;
|
||||
$skip -= $read_limit;
|
||||
}
|
||||
unset($skip);
|
||||
}
|
||||
|
||||
// This array contain the data like number of valid sql queries in the statement
|
||||
// and complete valid sql statement (which affected for rows)
|
||||
$sql_data = [
|
||||
'valid_sql' => [],
|
||||
'valid_queries' => 0,
|
||||
];
|
||||
|
||||
if (! $error) {
|
||||
/**
|
||||
* @var ImportPlugin $import_plugin
|
||||
*/
|
||||
$import_plugin = Plugins::getPlugin(
|
||||
'import',
|
||||
$format,
|
||||
'libraries/classes/Plugins/Import/',
|
||||
$import_type
|
||||
);
|
||||
if ($import_plugin == null) {
|
||||
$message = Message::error(
|
||||
__('Could not load import plugins, please check your installation!')
|
||||
);
|
||||
|
||||
$_SESSION['Import_message']['message'] = $message->getDisplay();
|
||||
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON('message', $message->getDisplay());
|
||||
$this->response->addHTML($message->getDisplay());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Do the real import
|
||||
$default_fk_check = Util::handleDisableFKCheckInit();
|
||||
try {
|
||||
$import_plugin->doImport($importHandle ?? null, $sql_data);
|
||||
Util::handleDisableFKCheckCleanup($default_fk_check);
|
||||
} catch (Throwable $e) {
|
||||
Util::handleDisableFKCheckCleanup($default_fk_check);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($importHandle)) {
|
||||
$importHandle->close();
|
||||
}
|
||||
|
||||
// Reset charset back, if we did some changes
|
||||
if ($reset_charset) {
|
||||
$this->dbi->query('SET CHARACTER SET ' . $charset_connection);
|
||||
$this->dbi->setCollation($collation_connection);
|
||||
}
|
||||
|
||||
// Show correct message
|
||||
if (! empty($id_bookmark) && $_POST['action_bookmark'] == 2) {
|
||||
$message = Message::success(__('The bookmark has been deleted.'));
|
||||
$display_query = $import_text;
|
||||
$error = false; // unset error marker, it was used just to skip processing
|
||||
} elseif (! empty($id_bookmark) && $_POST['action_bookmark'] == 1) {
|
||||
$message = Message::notice(__('Showing bookmark'));
|
||||
} elseif ($bookmark_created) {
|
||||
$special_message = '[br]' . sprintf(
|
||||
__('Bookmark %s has been created.'),
|
||||
htmlspecialchars($_POST['bkm_label'])
|
||||
);
|
||||
} elseif ($finished && ! $error) {
|
||||
// Do not display the query with message, we do it separately
|
||||
$display_query = ';';
|
||||
if ($import_type !== 'query') {
|
||||
$message = Message::success(
|
||||
'<em>'
|
||||
. _ngettext(
|
||||
'Import has been successfully finished, %d query executed.',
|
||||
'Import has been successfully finished, %d queries executed.',
|
||||
$executed_queries
|
||||
)
|
||||
. '</em>'
|
||||
);
|
||||
$message->addParam($executed_queries);
|
||||
|
||||
if (! empty($import_notice)) {
|
||||
$message->addHtml($import_notice);
|
||||
}
|
||||
if (! empty($local_import_file)) {
|
||||
$message->addText('(' . $local_import_file . ')');
|
||||
} else {
|
||||
$message->addText('(' . $_FILES['import_file']['name'] . ')');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Did we hit timeout? Tell it user.
|
||||
if ($timeout_passed) {
|
||||
$url_params['timeout_passed'] = '1';
|
||||
$url_params['offset'] = $offset;
|
||||
if (isset($local_import_file)) {
|
||||
$url_params['local_import_file'] = $local_import_file;
|
||||
}
|
||||
|
||||
$importUrl = $err_url = $goto . Url::getCommon($url_params, '&');
|
||||
|
||||
$message = Message::error(
|
||||
__(
|
||||
'Script timeout passed, if you want to finish import,'
|
||||
. ' please %sresubmit the same file%s and import will resume.'
|
||||
)
|
||||
);
|
||||
$message->addParamHtml('<a href="' . $importUrl . '">');
|
||||
$message->addParamHtml('</a>');
|
||||
|
||||
if ($offset == 0 || (isset($original_skip) && $original_skip == $offset)) {
|
||||
$message->addText(
|
||||
__(
|
||||
'However on last run no data has been parsed,'
|
||||
. ' this usually means phpMyAdmin won\'t be able to'
|
||||
. ' finish this import unless you increase php time limits.'
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// if there is any message, copy it into $_SESSION as well,
|
||||
// so we can obtain it by AJAX call
|
||||
if (isset($message)) {
|
||||
$_SESSION['Import_message']['message'] = $message->getDisplay();
|
||||
}
|
||||
// Parse and analyze the query, for correct db and table name
|
||||
// in case of a query typed in the query window
|
||||
// (but if the query is too large, in case of an imported file, the parser
|
||||
// can choke on it so avoid parsing)
|
||||
$sqlLength = mb_strlen($sql_query);
|
||||
if ($sqlLength <= $cfg['MaxCharactersInDisplayedSQL']) {
|
||||
[
|
||||
$analyzed_sql_results,
|
||||
$db,
|
||||
$table_from_sql,
|
||||
] = ParseAnalyze::sqlQuery($sql_query, $db);
|
||||
|
||||
$reload = $analyzed_sql_results['reload'];
|
||||
$offset = $analyzed_sql_results['offset'];
|
||||
|
||||
if ($table != $table_from_sql && ! empty($table_from_sql)) {
|
||||
$table = $table_from_sql;
|
||||
}
|
||||
}
|
||||
|
||||
// There was an error?
|
||||
if (isset($my_die)) {
|
||||
foreach ($my_die as $key => $die) {
|
||||
Generator::mysqlDie(
|
||||
$die['error'],
|
||||
$die['sql'],
|
||||
false,
|
||||
$err_url,
|
||||
$error
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ($go_sql) {
|
||||
if (! empty($sql_data) && ($sql_data['valid_queries'] > 1)) {
|
||||
$_SESSION['is_multi_query'] = true;
|
||||
$sql_queries = $sql_data['valid_sql'];
|
||||
} else {
|
||||
$sql_queries = [$sql_query];
|
||||
}
|
||||
|
||||
$html_output = '';
|
||||
|
||||
foreach ($sql_queries as $sql_query) {
|
||||
// parse sql query
|
||||
[
|
||||
$analyzed_sql_results,
|
||||
$db,
|
||||
$table_from_sql,
|
||||
] = ParseAnalyze::sqlQuery($sql_query, $db);
|
||||
|
||||
$offset = $analyzed_sql_results['offset'];
|
||||
$reload = $analyzed_sql_results['reload'];
|
||||
|
||||
// Check if User is allowed to issue a 'DROP DATABASE' Statement
|
||||
if ($this->sql->hasNoRightsToDropDatabase(
|
||||
$analyzed_sql_results,
|
||||
$cfg['AllowUserDropDatabase'],
|
||||
$this->dbi->isSuperUser()
|
||||
)) {
|
||||
Generator::mysqlDie(
|
||||
__('"DROP DATABASE" statements are disabled.'),
|
||||
'',
|
||||
false,
|
||||
$_SESSION['Import_message']['go_back_url']
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($table != $table_from_sql && ! empty($table_from_sql)) {
|
||||
$table = $table_from_sql;
|
||||
}
|
||||
|
||||
$html_output .= $this->sql->executeQueryAndGetQueryResponse(
|
||||
$analyzed_sql_results, // analyzed_sql_results
|
||||
false, // is_gotofile
|
||||
$db, // db
|
||||
$table, // table
|
||||
null, // find_real_end
|
||||
null, // sql_query_for_bookmark - see below
|
||||
null, // extra_data
|
||||
null, // message_to_show
|
||||
null, // sql_data
|
||||
$goto, // goto
|
||||
$PMA_Theme->getImgPath(),
|
||||
null, // disp_query
|
||||
null, // disp_message
|
||||
$sql_query, // sql_query
|
||||
null // complete_query
|
||||
);
|
||||
}
|
||||
|
||||
// sql_query_for_bookmark is not included in Sql::executeQueryAndGetQueryResponse
|
||||
// since only one bookmark has to be added for all the queries submitted through
|
||||
// the SQL tab
|
||||
if (! empty($_POST['bkm_label']) && ! empty($import_text)) {
|
||||
$cfgBookmark = Bookmark::getParams($cfg['Server']['user']);
|
||||
|
||||
if (! is_array($cfgBookmark)) {
|
||||
$cfgBookmark = [];
|
||||
}
|
||||
|
||||
$this->sql->storeTheQueryAsBookmark(
|
||||
$db,
|
||||
$cfgBookmark['user'],
|
||||
$_POST['sql_query'],
|
||||
$_POST['bkm_label'],
|
||||
isset($_POST['bkm_replace'])
|
||||
);
|
||||
}
|
||||
|
||||
$this->response->addJSON('ajax_reload', $ajax_reload);
|
||||
$this->response->addHTML($html_output);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($result) {
|
||||
// Save a Bookmark with more than one queries (if Bookmark label given).
|
||||
if (! empty($_POST['bkm_label']) && ! empty($import_text)) {
|
||||
$cfgBookmark = Bookmark::getParams($cfg['Server']['user']);
|
||||
|
||||
if (! is_array($cfgBookmark)) {
|
||||
$cfgBookmark = [];
|
||||
}
|
||||
|
||||
$this->sql->storeTheQueryAsBookmark(
|
||||
$db,
|
||||
$cfgBookmark['user'],
|
||||
$_POST['sql_query'],
|
||||
$_POST['bkm_label'],
|
||||
isset($_POST['bkm_replace'])
|
||||
);
|
||||
}
|
||||
|
||||
$this->response->setRequestStatus(true);
|
||||
$this->response->addJSON('message', Message::success($msg));
|
||||
$this->response->addJSON(
|
||||
'sql_query',
|
||||
Generator::getMessage($msg, $sql_query, 'success')
|
||||
);
|
||||
} elseif ($result === false) {
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON('message', Message::error($msg));
|
||||
} else {
|
||||
$active_page = $goto;
|
||||
include ROOT_PATH . $goto;
|
||||
}
|
||||
|
||||
// If there is request for ROLLBACK in the end.
|
||||
if (! isset($_POST['rollback_query'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->dbi->query('ROLLBACK');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers;
|
||||
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\Import\Ajax;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\Template;
|
||||
use function header;
|
||||
use function ini_get;
|
||||
use function session_start;
|
||||
use function session_write_close;
|
||||
use function time;
|
||||
use function usleep;
|
||||
|
||||
/**
|
||||
* Import progress bar backend
|
||||
*/
|
||||
class ImportStatusController
|
||||
{
|
||||
/** @var Template */
|
||||
private $template;
|
||||
|
||||
/**
|
||||
* @param Template $template Template object
|
||||
*/
|
||||
public function __construct(Template $template)
|
||||
{
|
||||
$this->template = $template;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $SESSION_KEY, $upload_id, $plugins, $timestamp;
|
||||
|
||||
[
|
||||
$SESSION_KEY,
|
||||
$upload_id,
|
||||
$plugins,
|
||||
] = Ajax::uploadProgressSetup();
|
||||
|
||||
// $_GET["message"] is used for asking for an import message
|
||||
if (isset($_GET['message']) && $_GET['message']) {
|
||||
// AJAX requests can't be cached!
|
||||
Core::noCacheHeader();
|
||||
|
||||
header('Content-type: text/html');
|
||||
|
||||
// wait 0.3 sec before we check for $_SESSION variable
|
||||
usleep(300000);
|
||||
|
||||
$maximumTime = ini_get('max_execution_time');
|
||||
$timestamp = time();
|
||||
// wait until message is available
|
||||
while (($_SESSION['Import_message']['message'] ?? null) == null) {
|
||||
// close session before sleeping
|
||||
session_write_close();
|
||||
// sleep
|
||||
usleep(250000); // 0.25 sec
|
||||
// reopen session
|
||||
session_start();
|
||||
|
||||
if (time() - $timestamp > $maximumTime) {
|
||||
$_SESSION['Import_message']['message'] = Message::error(
|
||||
__('Could not load the progress of the import.')
|
||||
)->getDisplay();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
echo $_SESSION['Import_message']['message'] ?? '';
|
||||
|
||||
if (isset($_SESSION['Import_message']['go_back_url'])) {
|
||||
echo $this->template->render('import_status', [
|
||||
'go_back_url' => $_SESSION['Import_message']['go_back_url'],
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
Ajax::status($_GET['id']);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,738 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers;
|
||||
|
||||
use function json_encode;
|
||||
|
||||
/**
|
||||
* Exporting of translated messages from PHP to JavaScript.
|
||||
*/
|
||||
final class JavaScriptMessagesController
|
||||
{
|
||||
/** @var array<string, string> */
|
||||
private $messages = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->setMessages();
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
echo 'var Messages = ' . json_encode($this->messages) . ';';
|
||||
}
|
||||
|
||||
private function setMessages(): void
|
||||
{
|
||||
global $cfg, $PMA_Theme;
|
||||
|
||||
$ajaxClockSmallGifPath = $PMA_Theme !== null ? $PMA_Theme->getImgPath('ajax_clock_small.gif') : '';
|
||||
|
||||
$this->messages = [
|
||||
/* For confirmations */
|
||||
'strConfirm' => __('Confirm'),
|
||||
'strDoYouReally' => __('Do you really want to execute "%s"?'),
|
||||
'strDropDatabaseStrongWarning' => __('You are about to DESTROY a complete database!'),
|
||||
'strDatabaseRenameToSameName' => __(
|
||||
'Cannot rename database to the same name. Change the name and try again'
|
||||
),
|
||||
'strDropTableStrongWarning' => __('You are about to DESTROY a complete table!'),
|
||||
'strTruncateTableStrongWarning' => __('You are about to TRUNCATE a complete table!'),
|
||||
'strDeleteTrackingData' => __('Delete tracking data for this table?'),
|
||||
'strDeleteTrackingDataMultiple' => __('Delete tracking data for these tables?'),
|
||||
'strDeleteTrackingVersion' => __('Delete tracking data for this version?'),
|
||||
'strDeleteTrackingVersionMultiple' => __('Delete tracking data for these versions?'),
|
||||
'strDeletingTrackingEntry' => __('Delete entry from tracking report?'),
|
||||
'strDeletingTrackingData' => __('Deleting tracking data'),
|
||||
'strDroppingPrimaryKeyIndex' => __('Dropping Primary Key/Index'),
|
||||
'strDroppingForeignKey' => __('Dropping Foreign key.'),
|
||||
'strOperationTakesLongTime' => __('This operation could take a long time. Proceed anyway?'),
|
||||
'strDropUserGroupWarning' => __('Do you really want to delete user group "%s"?'),
|
||||
'strConfirmDeleteQBESearch' => __('Do you really want to delete the search "%s"?'),
|
||||
'strConfirmNavigation' => __('You have unsaved changes; are you sure you want to leave this page?'),
|
||||
'strConfirmRowChange' => __(
|
||||
'You are trying to reduce the number of rows, but have already entered'
|
||||
. ' data in those rows which will be lost. Do you wish to continue?'
|
||||
),
|
||||
'strDropUserWarning' => __('Do you really want to revoke the selected user(s) ?'),
|
||||
'strDeleteCentralColumnWarning' => __('Do you really want to delete this central column?'),
|
||||
'strDropRTEitems' => __('Do you really want to delete the selected items?'),
|
||||
'strDropPartitionWarning' => __(
|
||||
'Do you really want to DROP the selected partition(s)? This will also DELETE ' .
|
||||
'the data related to the selected partition(s)!'
|
||||
),
|
||||
'strTruncatePartitionWarning' => __('Do you really want to TRUNCATE the selected partition(s)?'),
|
||||
'strRemovePartitioningWarning' => __('Do you really want to remove partitioning?'),
|
||||
'strResetSlaveWarning' => __('Do you really want to RESET SLAVE?'),
|
||||
'strChangeColumnCollation' => __(
|
||||
'This operation will attempt to convert your data to the new collation. In '
|
||||
. 'rare cases, especially where a character doesn\'t exist in the new '
|
||||
. 'collation, this process could cause the data to appear incorrectly under '
|
||||
. 'the new collation; in this case we suggest you revert to the original '
|
||||
. 'collation and refer to the tips at '
|
||||
)
|
||||
. '<a href="%s" target="garbled_data_wiki">' . __('Garbled Data') . '</a>.'
|
||||
. '<br><br>'
|
||||
. __('Are you sure you wish to change the collation and convert the data?'),
|
||||
|
||||
'strChangeAllColumnCollationsWarning' => __(
|
||||
'Through this operation, MySQL attempts to map the data values between '
|
||||
. 'collations. If the character sets are incompatible, there may be data loss '
|
||||
. 'and this lost data may <b>NOT</b> be recoverable simply by changing back the '
|
||||
. 'column collation(s). <b>To convert existing data, it is suggested to use the '
|
||||
. 'column(s) editing feature (the "Change" Link) on the table structure page. '
|
||||
. '</b>'
|
||||
)
|
||||
. '<br><br>'
|
||||
. __(
|
||||
'Are you sure you wish to change all the column collations and convert the data?'
|
||||
),
|
||||
|
||||
/* For modal dialog buttons */
|
||||
'strSaveAndClose' => __('Save & close'),
|
||||
'strReset' => __('Reset'),
|
||||
'strResetAll' => __('Reset all'),
|
||||
|
||||
/* For indexes */
|
||||
'strFormEmpty' => __('Missing value in the form!'),
|
||||
'strRadioUnchecked' => __('Select at least one of the options!'),
|
||||
'strEnterValidNumber' => __('Please enter a valid number!'),
|
||||
'strEnterValidLength' => __('Please enter a valid length!'),
|
||||
'strAddIndex' => __('Add index'),
|
||||
'strEditIndex' => __('Edit index'),
|
||||
/* l10n: Rename a table Index */
|
||||
'strRenameIndex' => __('Rename index'),
|
||||
'strAddToIndex' => __('Add %s column(s) to index'),
|
||||
'strCreateSingleColumnIndex' => __('Create single-column index'),
|
||||
'strCreateCompositeIndex' => __('Create composite index'),
|
||||
'strCompositeWith' => __('Composite with:'),
|
||||
'strMissingColumn' => __('Please select column(s) for the index.'),
|
||||
|
||||
/* For Preview SQL*/
|
||||
'strPreviewSQL' => __('Preview SQL'),
|
||||
|
||||
/* For Simulate DML*/
|
||||
'strSimulateDML' => __('Simulate query'),
|
||||
'strMatchedRows' => __('Matched rows:'),
|
||||
'strSQLQuery' => __('SQL query:'),
|
||||
|
||||
/* Charts */
|
||||
/* l10n: Default label for the y-Axis of Charts */
|
||||
'strYValues' => __('Y values'),
|
||||
|
||||
/* Database multi-table query */
|
||||
'strEmptyQuery' => __('Please enter the SQL query first.'),
|
||||
|
||||
/* For server/privileges.js */
|
||||
'strHostEmpty' => __('The host name is empty!'),
|
||||
'strUserEmpty' => __('The user name is empty!'),
|
||||
'strPasswordEmpty' => __('The password is empty!'),
|
||||
'strPasswordNotSame' => __('The passwords aren\'t the same!'),
|
||||
'strRemovingSelectedUsers' => __('Removing Selected Users'),
|
||||
'strClose' => __('Close'),
|
||||
|
||||
/* For export.js */
|
||||
'strTemplateCreated' => __('Template was created.'),
|
||||
'strTemplateLoaded' => __('Template was loaded.'),
|
||||
'strTemplateUpdated' => __('Template was updated.'),
|
||||
'strTemplateDeleted' => __('Template was deleted.'),
|
||||
|
||||
/* l10n: Other, small valued, queries */
|
||||
'strOther' => __('Other'),
|
||||
/* l10n: Thousands separator */
|
||||
'strThousandsSeparator' => __(','),
|
||||
/* l10n: Decimal separator */
|
||||
'strDecimalSeparator' => __('.'),
|
||||
|
||||
'strChartConnectionsTitle' => __('Connections / Processes'),
|
||||
|
||||
/* server status monitor */
|
||||
'strIncompatibleMonitorConfig' => __('Local monitor configuration incompatible!'),
|
||||
'strIncompatibleMonitorConfigDescription' => __(
|
||||
'The chart arrangement configuration in your browsers local storage is not '
|
||||
. 'compatible anymore to the newer version of the monitor dialog. It is very '
|
||||
. 'likely that your current configuration will not work anymore. Please reset '
|
||||
. 'your configuration to default in the <i>Settings</i> menu.'
|
||||
),
|
||||
|
||||
'strQueryCacheEfficiency' => __('Query cache efficiency'),
|
||||
'strQueryCacheUsage' => __('Query cache usage'),
|
||||
'strQueryCacheUsed' => __('Query cache used'),
|
||||
|
||||
'strSystemCPUUsage' => __('System CPU usage'),
|
||||
'strSystemMemory' => __('System memory'),
|
||||
'strSystemSwap' => __('System swap'),
|
||||
|
||||
'strAverageLoad' => __('Average load'),
|
||||
'strTotalMemory' => __('Total memory'),
|
||||
'strCachedMemory' => __('Cached memory'),
|
||||
'strBufferedMemory' => __('Buffered memory'),
|
||||
'strFreeMemory' => __('Free memory'),
|
||||
'strUsedMemory' => __('Used memory'),
|
||||
|
||||
'strTotalSwap' => __('Total swap'),
|
||||
'strCachedSwap' => __('Cached swap'),
|
||||
'strUsedSwap' => __('Used swap'),
|
||||
'strFreeSwap' => __('Free swap'),
|
||||
|
||||
'strBytesSent' => __('Bytes sent'),
|
||||
'strBytesReceived' => __('Bytes received'),
|
||||
'strConnections' => __('Connections'),
|
||||
'strProcesses' => __('Processes'),
|
||||
|
||||
/* summary row */
|
||||
'strB' => __('B'),
|
||||
'strKiB' => __('KiB'),
|
||||
'strMiB' => __('MiB'),
|
||||
'strGiB' => __('GiB'),
|
||||
'strTiB' => __('TiB'),
|
||||
'strPiB' => __('PiB'),
|
||||
'strEiB' => __('EiB'),
|
||||
'strNTables' => __('%d table(s)'),
|
||||
|
||||
/* l10n: Questions is the name of a MySQL Status variable */
|
||||
'strQuestions' => __('Questions'),
|
||||
'strTraffic' => __('Traffic'),
|
||||
'strSettings' => __('Settings'),
|
||||
'strAddChart' => __('Add chart to grid'),
|
||||
'strAddOneSeriesWarning' => __('Please add at least one variable to the series!'),
|
||||
'strNone' => __('None'),
|
||||
/* l10n: SQL Query on modal to show exported query */
|
||||
'strQuery' => __('SQL Query'),
|
||||
'strResumeMonitor' => __('Resume monitor'),
|
||||
'strPauseMonitor' => __('Pause monitor'),
|
||||
'strStartRefresh' => __('Start auto refresh'),
|
||||
'strStopRefresh' => __('Stop auto refresh'),
|
||||
/* Monitor: Instructions Dialog */
|
||||
'strBothLogOn' => __('general_log and slow_query_log are enabled.'),
|
||||
'strGenLogOn' => __('general_log is enabled.'),
|
||||
'strSlowLogOn' => __('slow_query_log is enabled.'),
|
||||
'strBothLogOff' => __('slow_query_log and general_log are disabled.'),
|
||||
'strLogOutNotTable' => __('log_output is not set to TABLE.'),
|
||||
'strLogOutIsTable' => __('log_output is set to TABLE.'),
|
||||
'strSmallerLongQueryTimeAdvice' => __(
|
||||
'slow_query_log is enabled, but the server logs only queries that take longer '
|
||||
. 'than %d seconds. It is advisable to set this long_query_time 0-2 seconds, '
|
||||
. 'depending on your system.'
|
||||
),
|
||||
'strLongQueryTimeSet' => __('long_query_time is set to %d second(s).'),
|
||||
'strSettingsAppliedGlobal' => __(
|
||||
'Following settings will be applied globally and reset to default on server '
|
||||
. 'restart:'
|
||||
),
|
||||
/* l10n: %s is FILE or TABLE */
|
||||
'strSetLogOutput' => __('Set log_output to %s'),
|
||||
/* l10n: Enable in this context means setting a status variable to ON */
|
||||
'strEnableVar' => __('Enable %s'),
|
||||
/* l10n: Disable in this context means setting a status variable to OFF */
|
||||
'strDisableVar' => __('Disable %s'),
|
||||
/* l10n: %d seconds */
|
||||
'setSetLongQueryTime' => __('Set long_query_time to %d seconds.'),
|
||||
'strNoSuperUser' => __(
|
||||
'You can\'t change these variables. Please log in as root or contact'
|
||||
. ' your database administrator.'
|
||||
),
|
||||
'strChangeSettings' => __('Change settings'),
|
||||
'strCurrentSettings' => __('Current settings'),
|
||||
|
||||
'strChartTitle' => __('Chart title'),
|
||||
/* l10n: As in differential values */
|
||||
'strDifferential' => __('Differential'),
|
||||
'strDividedBy' => __('Divided by %s'),
|
||||
'strUnit' => __('Unit'),
|
||||
|
||||
'strFromSlowLog' => __('From slow log'),
|
||||
'strFromGeneralLog' => __('From general log'),
|
||||
'strServerLogError' => __(
|
||||
'The database name is not known for this query in the server\'s logs.'
|
||||
),
|
||||
'strAnalysingLogsTitle' => __('Analysing logs'),
|
||||
'strAnalysingLogs' => __('Analysing & loading logs. This may take a while.'),
|
||||
'strCancelRequest' => __('Cancel request'),
|
||||
'strCountColumnExplanation' => __(
|
||||
'This column shows the amount of identical queries that are grouped together. '
|
||||
. 'However only the SQL query itself has been used as a grouping criteria, so '
|
||||
. 'the other attributes of queries, such as start time, may differ.'
|
||||
),
|
||||
'strMoreCountColumnExplanation' => __(
|
||||
'Since grouping of INSERTs queries has been selected, INSERT queries into the '
|
||||
. 'same table are also being grouped together, disregarding of the inserted '
|
||||
. 'data.'
|
||||
),
|
||||
'strLogDataLoaded' => __('Log data loaded. Queries executed in this time span:'),
|
||||
|
||||
'strJumpToTable' => __('Jump to Log table'),
|
||||
'strNoDataFoundTitle' => __('No data found'),
|
||||
'strNoDataFound' => __('Log analysed, but no data found in this time span.'),
|
||||
|
||||
'strAnalyzing' => __('Analyzing…'),
|
||||
'strExplainOutput' => __('Explain output'),
|
||||
'strStatus' => __('Status'),
|
||||
'strTime' => __('Time'),
|
||||
'strTotalTime' => __('Total time:'),
|
||||
'strProfilingResults' => __('Profiling results'),
|
||||
'strTable' => _pgettext('Display format', 'Table'),
|
||||
'strChart' => __('Chart'),
|
||||
|
||||
'strAliasDatabase' => _pgettext('Alias', 'Database'),
|
||||
'strAliasTable' => _pgettext('Alias', 'Table'),
|
||||
'strAliasColumn' => _pgettext('Alias', 'Column'),
|
||||
|
||||
/* l10n: A collection of available filters */
|
||||
'strFiltersForLogTable' => __('Log table filter options'),
|
||||
/* l10n: Filter as in "Start Filtering" */
|
||||
'strFilter' => __('Filter'),
|
||||
'strFilterByWordRegexp' => __('Filter queries by word/regexp:'),
|
||||
'strIgnoreWhereAndGroup' => __('Group queries, ignoring variable data in WHERE clauses'),
|
||||
'strSumRows' => __('Sum of grouped rows:'),
|
||||
'strTotal' => __('Total:'),
|
||||
|
||||
'strLoadingLogs' => __('Loading logs'),
|
||||
'strRefreshFailed' => __('Monitor refresh failed'),
|
||||
'strInvalidResponseExplanation' => __(
|
||||
'While requesting new chart data the server returned an invalid response. This '
|
||||
. 'is most likely because your session expired. Reloading the page and '
|
||||
. 'reentering your credentials should help.'
|
||||
),
|
||||
'strReloadPage' => __('Reload page'),
|
||||
|
||||
'strAffectedRows' => __('Affected rows:'),
|
||||
|
||||
'strFailedParsingConfig' => __(
|
||||
'Failed parsing config file. It doesn\'t seem to be valid JSON code.'
|
||||
),
|
||||
'strFailedBuildingGrid' => __(
|
||||
'Failed building chart grid with imported config. Resetting to default config…'
|
||||
),
|
||||
'strImport' => __('Import'),
|
||||
'strImportDialogTitle' => __('Import monitor configuration'),
|
||||
'strImportDialogMessage' => __('Please select the file you want to import.'),
|
||||
'strTableNameDialogMessage' => __('Please enter a valid table name.'),
|
||||
'strDBNameDialogMessage' => __('Please enter a valid database name.'),
|
||||
'strNoImportFile' => __('No files available on server for import!'),
|
||||
|
||||
'strAnalyzeQuery' => __('Analyse query'),
|
||||
|
||||
/* For query editor */
|
||||
'strFormatting' => __('Formatting SQL…'),
|
||||
'strNoParam' => __('No parameters found!'),
|
||||
|
||||
/* For inline query editing */
|
||||
'strGo' => __('Go'),
|
||||
'strCancel' => __('Cancel'),
|
||||
|
||||
/* For page-related settings */
|
||||
'strPageSettings' => __('Page-related settings'),
|
||||
'strApply' => __('Apply'),
|
||||
|
||||
/* For Ajax Notifications */
|
||||
'strLoading' => __('Loading…'),
|
||||
'strAbortedRequest' => __('Request aborted!!'),
|
||||
'strProcessingRequest' => __('Processing request'),
|
||||
'strRequestFailed' => __('Request failed!!'),
|
||||
'strErrorProcessingRequest' => __('Error in processing request'),
|
||||
'strErrorCode' => __('Error code: %s'),
|
||||
'strErrorText' => __('Error text: %s'),
|
||||
'strErrorConnection' => __(
|
||||
'It seems that the connection to server has been lost. Please check your ' .
|
||||
'network connectivity and server status.'
|
||||
),
|
||||
'strNoDatabasesSelected' => __('No databases selected.'),
|
||||
'strNoAccountSelected' => __('No accounts selected.'),
|
||||
'strDroppingColumn' => __('Dropping column'),
|
||||
'strAddingPrimaryKey' => __('Adding primary key'),
|
||||
'strOK' => __('OK'),
|
||||
'strDismiss' => __('Click to dismiss this notification'),
|
||||
|
||||
/* For database/operations.js */
|
||||
'strRenamingDatabases' => __('Renaming databases'),
|
||||
'strCopyingDatabase' => __('Copying database'),
|
||||
'strChangingCharset' => __('Changing charset'),
|
||||
'strNo' => __('No'),
|
||||
|
||||
/* For Foreign key checks */
|
||||
'strForeignKeyCheck' => __('Enable foreign key checks'),
|
||||
|
||||
/* For database/structure.js */
|
||||
'strErrorRealRowCount' => __('Failed to get real row count.'),
|
||||
|
||||
/* For database/search.js */
|
||||
'strSearching' => __('Searching'),
|
||||
'strHideSearchResults' => __('Hide search results'),
|
||||
'strShowSearchResults' => __('Show search results'),
|
||||
'strBrowsing' => __('Browsing'),
|
||||
'strDeleting' => __('Deleting'),
|
||||
'strConfirmDeleteResults' => __('Delete the matches for the %s table?'),
|
||||
|
||||
/* For rte.js */
|
||||
'MissingReturn' => __('The definition of a stored function must contain a RETURN statement!'),
|
||||
'strExport' => __('Export'),
|
||||
'NoExportable' => __('No routine is exportable. Required privileges may be lacking.'),
|
||||
|
||||
/* For ENUM/SET editor*/
|
||||
'enum_editor' => __('ENUM/SET editor'),
|
||||
'enum_columnVals' => __('Values for column %s'),
|
||||
'enum_newColumnVals' => __('Values for a new column'),
|
||||
'enum_hint' => __('Enter each value in a separate field.'),
|
||||
'enum_addValue' => __('Add %d value(s)'),
|
||||
|
||||
/* For import.js */
|
||||
'strImportCSV' => __(
|
||||
'Note: If the file contains multiple tables, they will be combined into one.'
|
||||
),
|
||||
|
||||
/* For sql.js */
|
||||
'strHideQueryBox' => __('Hide query box'),
|
||||
'strShowQueryBox' => __('Show query box'),
|
||||
'strEdit' => __('Edit'),
|
||||
'strDelete' => __('Delete'),
|
||||
'strNotValidRowNumber' => __('%d is not valid row number.'),
|
||||
'strBrowseForeignValues' => __('Browse foreign values'),
|
||||
'strNoAutoSavedQuery' => __('No previously auto-saved query is available. Loading default query.'),
|
||||
'strPreviousSaveQuery' => __(
|
||||
'You have a previously saved query. Click Get auto-saved query to load the query.'
|
||||
),
|
||||
'strBookmarkVariable' => __('Variable %d:'),
|
||||
|
||||
/* For Central list of columns */
|
||||
'pickColumn' => __('Pick'),
|
||||
'pickColumnTitle' => __('Column selector'),
|
||||
'searchList' => __('Search this list'),
|
||||
'strEmptyCentralList' => __(
|
||||
'No columns in the central list. Make sure the Central columns list for '
|
||||
. 'database %s has columns that are not present in the current table.'
|
||||
),
|
||||
'seeMore' => __('See more'),
|
||||
'confirmTitle' => __('Are you sure?'),
|
||||
'makeConsistentMessage' => __(
|
||||
'This action may change some of the columns definition.<br>Are you sure you '
|
||||
. 'want to continue?'
|
||||
),
|
||||
'strContinue' => __('Continue'),
|
||||
|
||||
/** For normalization */
|
||||
'strAddPrimaryKey' => __('Add primary key'),
|
||||
'strPrimaryKeyAdded' => __('Primary key added.'),
|
||||
'strToNextStep' => __('Taking you to next step…'),
|
||||
'strFinishMsg' => __("The first step of normalization is complete for table '%s'."),
|
||||
'strEndStep' => __('End of step'),
|
||||
'str2NFNormalization' => __('Second step of normalization (2NF)'),
|
||||
'strDone' => __('Done'),
|
||||
'strConfirmPd' => __('Confirm partial dependencies'),
|
||||
'strSelectedPd' => __('Selected partial dependencies are as follows:'),
|
||||
'strPdHintNote' => __(
|
||||
'Note: a, b -> d,f implies values of columns a and b combined together can '
|
||||
. 'determine values of column d and column f.'
|
||||
),
|
||||
'strNoPdSelected' => __('No partial dependencies selected!'),
|
||||
'strBack' => __('Back'),
|
||||
'strShowPossiblePd' => __('Show me the possible partial dependencies based on data in the table'),
|
||||
'strHidePd' => __('Hide partial dependencies list'),
|
||||
'strWaitForPd' => __(
|
||||
'Sit tight! It may take few seconds depending on data size and column count of '
|
||||
. 'the table.'
|
||||
),
|
||||
'strStep' => __('Step'),
|
||||
'strMoveRepeatingGroup' => '<ol><b>' . __('The following actions will be performed:') . '</b>'
|
||||
. '<li>' . __('DROP columns %s from the table %s') . '</li>'
|
||||
. '<li>' . __('Create the following table') . '</li>',
|
||||
'strNewTablePlaceholder' => 'Enter new table name',
|
||||
'strNewColumnPlaceholder' => 'Enter column name',
|
||||
'str3NFNormalization' => __('Third step of normalization (3NF)'),
|
||||
'strConfirmTd' => __('Confirm transitive dependencies'),
|
||||
'strSelectedTd' => __('Selected dependencies are as follows:'),
|
||||
'strNoTdSelected' => __('No dependencies selected!'),
|
||||
|
||||
/* For server/variables.js */
|
||||
'strSave' => __('Save'),
|
||||
|
||||
/* For table/select.js */
|
||||
'strHideSearchCriteria' => __('Hide search criteria'),
|
||||
'strShowSearchCriteria' => __('Show search criteria'),
|
||||
'strRangeSearch' => __('Range search'),
|
||||
'strColumnMax' => __('Column maximum:'),
|
||||
'strColumnMin' => __('Column minimum:'),
|
||||
'strMinValue' => __('Minimum value:'),
|
||||
'strMaxValue' => __('Maximum value:'),
|
||||
|
||||
/* For table/find_replace.js */
|
||||
'strHideFindNReplaceCriteria' => __('Hide find and replace criteria'),
|
||||
'strShowFindNReplaceCriteria' => __('Show find and replace criteria'),
|
||||
|
||||
/* For table/zoom_plot_jqplot.js */
|
||||
'strDisplayHelp' => '<ul><li>'
|
||||
. __('Each point represents a data row.')
|
||||
. '</li><li>'
|
||||
. __('Hovering over a point will show its label.')
|
||||
. '</li><li>'
|
||||
. __('To zoom in, select a section of the plot with the mouse.')
|
||||
. '</li><li>'
|
||||
. __('Click reset zoom button to come back to original state.')
|
||||
. '</li><li>'
|
||||
. __('Click a data point to view and possibly edit the data row.')
|
||||
. '</li><li>'
|
||||
. __('The plot can be resized by dragging it along the bottom right corner.')
|
||||
. '</li></ul>',
|
||||
'strHelpTitle' => 'Zoom search instructions',
|
||||
'strInputNull' => '<strong>' . __('Select two columns') . '</strong>',
|
||||
'strSameInputs' => '<strong>'
|
||||
. __('Select two different columns')
|
||||
. '</strong>',
|
||||
'strDataPointContent' => __('Data point content'),
|
||||
|
||||
/* For table/change.js */
|
||||
'strIgnore' => __('Ignore'),
|
||||
'strCopy' => __('Copy'),
|
||||
'strX' => __('X'),
|
||||
'strY' => __('Y'),
|
||||
'strPoint' => __('Point'),
|
||||
'strPointN' => __('Point %d'),
|
||||
'strLineString' => __('Linestring'),
|
||||
'strPolygon' => __('Polygon'),
|
||||
'strGeometry' => __('Geometry'),
|
||||
'strInnerRing' => __('Inner ring'),
|
||||
'strOuterRing' => __('Outer ring'),
|
||||
'strAddPoint' => __('Add a point'),
|
||||
'strAddInnerRing' => __('Add an inner ring'),
|
||||
'strYes' => __('Yes'),
|
||||
'strCopyEncryptionKey' => __('Do you want to copy encryption key?'),
|
||||
'strEncryptionKey' => __('Encryption key'),
|
||||
/* l10n: Tip for HEX conversion of Integers */
|
||||
'HexConversionInfo' => __(
|
||||
'The HEX function will treat the integer as a string while calculating the hexadecimal value'
|
||||
),
|
||||
|
||||
/* For Tip to be shown on Time field */
|
||||
'strMysqlAllowedValuesTipTime' => __(
|
||||
'MySQL accepts additional values not selectable by the slider;'
|
||||
. ' key in those values directly if desired'
|
||||
),
|
||||
|
||||
/* For Tip to be shown on Date field */
|
||||
'strMysqlAllowedValuesTipDate' => __(
|
||||
'MySQL accepts additional values not selectable by the datepicker;'
|
||||
. ' key in those values directly if desired'
|
||||
),
|
||||
|
||||
/* For Lock symbol Tooltip */
|
||||
'strLockToolTip' => __(
|
||||
'Indicates that you have made changes to this page;'
|
||||
. ' you will be prompted for confirmation before abandoning changes'
|
||||
),
|
||||
|
||||
/* Designer (js/designer/move.js) */
|
||||
'strSelectReferencedKey' => __('Select referenced key'),
|
||||
'strSelectForeignKey' => __('Select Foreign Key'),
|
||||
'strPleaseSelectPrimaryOrUniqueKey' => __('Please select the primary key or a unique key!'),
|
||||
'strChangeDisplay' => __('Choose column to display'),
|
||||
'strLeavingDesigner' => __(
|
||||
'You haven\'t saved the changes in the layout. They will be lost if you'
|
||||
. ' don\'t save them. Do you want to continue?'
|
||||
),
|
||||
'strQueryEmpty' => __('value/subQuery is empty'),
|
||||
'strAddTables' => __('Add tables from other databases'),
|
||||
'strPageName' => __('Page name'),
|
||||
'strSavePage' => __('Save page'),
|
||||
'strSavePageAs' => __('Save page as'),
|
||||
'strOpenPage' => __('Open page'),
|
||||
'strDeletePage' => __('Delete page'),
|
||||
/* l10n: When the user opens a page saved in the Designer */
|
||||
'strSavedPageTableMissing' => __('Some tables saved in this page might have been renamed or deleted.'),
|
||||
'strUntitled' => __('Untitled'),
|
||||
'strSelectPage' => __('Please select a page to continue'),
|
||||
'strEnterValidPageName' => __('Please enter a valid page name'),
|
||||
'strLeavingPage' => __('Do you want to save the changes to the current page?'),
|
||||
'strSuccessfulPageDelete' => __('Successfully deleted the page'),
|
||||
'strExportRelationalSchema' => __('Export relational schema'),
|
||||
'strModificationSaved' => __('Modifications have been saved'),
|
||||
|
||||
/* Visual query builder (js/designer/move.js) */
|
||||
'strObjectsCreated' => __('%d object(s) created.'),
|
||||
'strColumnName' => __('Column name'),
|
||||
'strSubmit' => __('Submit'),
|
||||
|
||||
/* For makegrid.js (column reordering, show/hide column, grid editing) */
|
||||
'strCellEditHint' => __('Press escape to cancel editing.'),
|
||||
'strSaveCellWarning' => __(
|
||||
'You have edited some data and they have not been saved. Are you sure you want '
|
||||
. 'to leave this page before saving the data?'
|
||||
),
|
||||
'strColOrderHint' => __('Drag to reorder.'),
|
||||
'strSortHint' => __('Click to sort results by this column.'),
|
||||
'strMultiSortHint' => __(
|
||||
'Shift+Click to add this column to ORDER BY clause or to toggle ASC/DESC.'
|
||||
. '<br>- Ctrl+Click or Alt+Click (Mac: Shift+Option+Click) to remove column '
|
||||
. 'from ORDER BY clause'
|
||||
),
|
||||
'strColMarkHint' => __('Click to mark/unmark.'),
|
||||
'strColNameCopyHint' => __('Double-click to copy column name.'),
|
||||
'strColVisibHint' => __(
|
||||
'Click the drop-down arrow<br>to toggle column\'s visibility.'
|
||||
),
|
||||
'strShowAllCol' => __('Show all'),
|
||||
'strAlertNonUnique' => __(
|
||||
'This table does not contain a unique column. Features related to the grid '
|
||||
. 'edit, checkbox, Edit, Copy and Delete links may not work after saving.'
|
||||
),
|
||||
'strEnterValidHex' => __('Please enter a valid hexadecimal string. Valid characters are 0-9, A-F.'),
|
||||
'strShowAllRowsWarning' => __(
|
||||
'Do you really want to see all of the rows? For a big table this could crash '
|
||||
. 'the browser.'
|
||||
),
|
||||
'strOriginalLength' => __('Original length'),
|
||||
|
||||
/** Drag & Drop sql import messages */
|
||||
'dropImportMessageCancel' => __('cancel'),
|
||||
'dropImportMessageAborted' => __('Aborted'),
|
||||
'dropImportMessageFailed' => __('Failed'),
|
||||
'dropImportMessageSuccess' => __('Success'),
|
||||
'dropImportImportResultHeader' => __('Import status'),
|
||||
'dropImportDropFiles' => __('Drop files here'),
|
||||
'dropImportSelectDB' => __('Select database first'),
|
||||
|
||||
/* For Print view */
|
||||
'print' => __('Print'),
|
||||
'back' => __('Back'),
|
||||
|
||||
// this approach does not work when the parameter is changed via user prefs
|
||||
'strGridEditFeatureHint' => $cfg['GridEditing'] === 'double-click'
|
||||
? __('You can also edit most values<br>by double-clicking directly on them.')
|
||||
: ($cfg['GridEditing'] === 'click'
|
||||
? __('You can also edit most values<br>by clicking directly on them.')
|
||||
: ''),
|
||||
|
||||
'strGoToLink' => __('Go to link:'),
|
||||
'strColNameCopyTitle' => __('Copy column name.'),
|
||||
'strColNameCopyText' => __('Right-click the column name to copy it to your clipboard.'),
|
||||
|
||||
/* password generation */
|
||||
'strGeneratePassword' => __('Generate password'),
|
||||
'strGenerate' => __('Generate'),
|
||||
'strChangePassword' => __('Change password'),
|
||||
|
||||
/* navigation tabs */
|
||||
'strMore' => __('More'),
|
||||
|
||||
/* navigation panel */
|
||||
'strShowPanel' => __('Show panel'),
|
||||
'strHidePanel' => __('Hide panel'),
|
||||
'strUnhideNavItem' => __('Show hidden navigation tree items.'),
|
||||
'linkWithMain' => __('Link with main panel'),
|
||||
'unlinkWithMain' => __('Unlink from main panel'),
|
||||
|
||||
/* microhistory */
|
||||
'strInvalidPage' => __('The requested page was not found in the history, it may have expired.'),
|
||||
|
||||
/* update */
|
||||
'strNewerVersion' => __(
|
||||
'A newer version of phpMyAdmin is available and you should consider upgrading. '
|
||||
. 'The newest version is %s, released on %s.'
|
||||
),
|
||||
/* l10n: Latest available phpMyAdmin version */
|
||||
'strLatestAvailable' => __(', latest stable version:'),
|
||||
'strUpToDate' => __('up to date'),
|
||||
|
||||
'strCreateView' => __('Create view'),
|
||||
|
||||
/* Error Reporting */
|
||||
'strSendErrorReport' => __('Send error report'),
|
||||
'strSubmitErrorReport' => __('Submit error report'),
|
||||
'strErrorOccurred' => __(
|
||||
'A fatal JavaScript error has occurred. Would you like to send an error report?'
|
||||
),
|
||||
'strChangeReportSettings' => __('Change report settings'),
|
||||
'strShowReportDetails' => __('Show report details'),
|
||||
'strTimeOutError' => __(
|
||||
'Your export is incomplete, due to a low execution time limit at the PHP level!'
|
||||
),
|
||||
|
||||
'strTooManyInputs' => __(
|
||||
'Warning: a form on this page has more than %d fields. On submission, '
|
||||
. "some of the fields might be ignored, due to PHP's "
|
||||
. 'max_input_vars configuration.'
|
||||
),
|
||||
|
||||
'phpErrorsFound' => '<div class="alert alert-danger" role="alert">'
|
||||
. __('Some errors have been detected on the server!')
|
||||
. '<br>'
|
||||
. __('Please look at the bottom of this window.')
|
||||
. '<div>'
|
||||
. '<input id="pma_ignore_errors_popup" type="submit" value="'
|
||||
. __('Ignore')
|
||||
. '" class="btn btn-secondary floatright message_errors_found">'
|
||||
. '<input id="pma_ignore_all_errors_popup" type="submit" value="'
|
||||
. __('Ignore All')
|
||||
. '" class="btn btn-secondary floatright message_errors_found">'
|
||||
. '</div></div>',
|
||||
|
||||
'phpErrorsBeingSubmitted' => '<div class="alert alert-danger" role="alert">'
|
||||
. __('Some errors have been detected on the server!')
|
||||
. '<br>'
|
||||
. __(
|
||||
'As per your settings, they are being submitted currently, please be '
|
||||
. 'patient.'
|
||||
)
|
||||
. '<br>'
|
||||
. '<img src="'
|
||||
. $ajaxClockSmallGifPath
|
||||
. '" width="16" height="16" alt="ajax clock">'
|
||||
. '</div>',
|
||||
'strCopyQueryButtonSuccess' => __('Successfully copied!'),
|
||||
'strCopyQueryButtonFailure' => __('Copying failed!'),
|
||||
|
||||
// For console
|
||||
'strConsoleRequeryConfirm' => __('Execute this query again?'),
|
||||
'strConsoleDeleteBookmarkConfirm' => __('Do you really want to delete this bookmark?'),
|
||||
'strConsoleDebugError' => __('Some error occurred while getting SQL debug info.'),
|
||||
'strConsoleDebugSummary' => __('%s queries executed %s times in %s seconds.'),
|
||||
'strConsoleDebugArgsSummary' => __('%s argument(s) passed'),
|
||||
'strConsoleDebugShowArgs' => __('Show arguments'),
|
||||
'strConsoleDebugHideArgs' => __('Hide arguments'),
|
||||
'strConsoleDebugTimeTaken' => __('Time taken:'),
|
||||
'strNoLocalStorage' => __(
|
||||
'There was a problem accessing your browser storage, some features may not'
|
||||
. ' work properly for you. It is likely that the browser doesn\'t support storage'
|
||||
. ' or the quota limit has been reached. In Firefox, corrupted storage can also'
|
||||
. ' cause such a problem, clearing your "Offline Website Data" might help. In Safari,'
|
||||
. ' such problem is commonly caused by "Private Mode Browsing".'
|
||||
),
|
||||
// For modals in /database/structure
|
||||
'strCopyTablesTo' => __('Copy tables to'),
|
||||
'strAddPrefix' => __('Add table prefix'),
|
||||
'strReplacePrefix' => __('Replace table with prefix'),
|
||||
'strCopyPrefix' => __('Copy table with prefix'),
|
||||
|
||||
/* For password strength simulation */
|
||||
'strExtrWeak' => __('Extremely weak'),
|
||||
'strVeryWeak' => __('Very weak'),
|
||||
'strWeak' => __('Weak'),
|
||||
'strGood' => __('Good'),
|
||||
'strStrong' => __('Strong'),
|
||||
|
||||
/* U2F errors */
|
||||
// l10n: error code 5 (from U2F API)
|
||||
'strU2FTimeout' => _pgettext('U2F error', 'Timed out waiting for security key activation.'),
|
||||
// l10n: error code 2 (from U2F API)
|
||||
'strU2FBadRequest' => _pgettext('U2F error', 'Invalid request sent to security key.'),
|
||||
// l10n: unknown error code (from U2F API)
|
||||
'strU2FUnknown' => _pgettext('U2F error', 'Unknown security key error.'),
|
||||
// l10n: error code 3 (from U2F API)
|
||||
'strU2FInvalidClient' => _pgettext('U2F error', 'Client does not support security key.'),
|
||||
// l10n: error code 4 (from U2F API) on register
|
||||
'strU2FErrorRegister' => _pgettext('U2F error', 'Failed security key activation.'),
|
||||
// l10n: error code 4 (from U2F API) on authanticate
|
||||
'strU2FErrorAuthenticate' => _pgettext('U2F error', 'Invalid security key.'),
|
||||
|
||||
/* Designer */
|
||||
'strTableAlreadyExists' => _pgettext(
|
||||
'The table already exists in the designer and can not be added once more.',
|
||||
'Table %s already exists!'
|
||||
),
|
||||
'strHide' => __('Hide'),
|
||||
'strShow' => __('Show'),
|
||||
'strStructure' => __('Structure'),
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
/**
|
||||
* Simple script to set correct charset for the license
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers;
|
||||
|
||||
use function is_readable;
|
||||
use function printf;
|
||||
use function readfile;
|
||||
|
||||
/**
|
||||
* Simple script to set correct charset for the license
|
||||
*/
|
||||
class LicenseController extends AbstractController
|
||||
{
|
||||
public function index(): void
|
||||
{
|
||||
$this->response->disable();
|
||||
$this->response->header('Content-type: text/plain; charset=utf-8');
|
||||
|
||||
$filename = LICENSE_FILE;
|
||||
|
||||
// Check if the file is available, some distributions remove these.
|
||||
if (@is_readable($filename)) {
|
||||
readfile($filename);
|
||||
} else {
|
||||
printf(
|
||||
__(
|
||||
'The %s file is not available on this system, please visit ' .
|
||||
'%s for more information.'
|
||||
),
|
||||
$filename,
|
||||
'https://www.phpmyadmin.net/'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
/**
|
||||
* Represents the interface between the linter and the query editor.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers;
|
||||
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\Linter;
|
||||
use function json_encode;
|
||||
|
||||
/**
|
||||
* Represents the interface between the linter and the query editor.
|
||||
*/
|
||||
class LintController extends AbstractController
|
||||
{
|
||||
public function index(): void
|
||||
{
|
||||
$params = [
|
||||
'sql_query' => $_POST['sql_query'] ?? null,
|
||||
'options' => $_POST['options'] ?? null,
|
||||
];
|
||||
|
||||
/**
|
||||
* The SQL query to be analyzed.
|
||||
*
|
||||
* This does not need to be checked again XSS or MySQL injections because it is
|
||||
* never executed, just parsed.
|
||||
*
|
||||
* The client, which will receive the JSON response will decode the message and
|
||||
* and any HTML fragments that are displayed to the user will be encoded anyway.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
$sqlQuery = ! empty($params['sql_query']) ? $params['sql_query'] : '';
|
||||
|
||||
$this->response->setAjax(true);
|
||||
|
||||
// Disabling standard response.
|
||||
$this->response->disable();
|
||||
|
||||
Core::headerJSON();
|
||||
|
||||
if (! empty($params['options'])) {
|
||||
$options = $params['options'];
|
||||
|
||||
if (! empty($options['routine_editor'])) {
|
||||
$sqlQuery = 'CREATE PROCEDURE `a`() ' . $sqlQuery;
|
||||
} elseif (! empty($options['trigger_editor'])) {
|
||||
$sqlQuery = 'CREATE TRIGGER `a` AFTER INSERT ON `b` FOR EACH ROW ' . $sqlQuery;
|
||||
} elseif (! empty($options['event_editor'])) {
|
||||
$sqlQuery = 'CREATE EVENT `a` ON SCHEDULE EVERY MINUTE DO ' . $sqlQuery;
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode(Linter::lint($sqlQuery));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers;
|
||||
|
||||
use PhpMyAdmin\Core;
|
||||
|
||||
class LogoutController
|
||||
{
|
||||
public function index(): void
|
||||
{
|
||||
global $auth_plugin, $token_mismatch;
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST' || $token_mismatch) {
|
||||
Core::sendHeaderLocation('./index.php?route=/');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$auth_plugin->logOut();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers;
|
||||
|
||||
use PhpMyAdmin\Config\PageSettings;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\Navigation\Navigation;
|
||||
use PhpMyAdmin\Relation;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Utils\SessionCache;
|
||||
|
||||
/**
|
||||
* The navigation panel
|
||||
*
|
||||
* Displays server, database and table selection tree.
|
||||
*/
|
||||
class NavigationController extends AbstractController
|
||||
{
|
||||
/** @var Navigation */
|
||||
private $navigation;
|
||||
|
||||
/** @var Relation */
|
||||
private $relation;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
*/
|
||||
public function __construct(
|
||||
$response,
|
||||
Template $template,
|
||||
Navigation $navigation,
|
||||
Relation $relation
|
||||
) {
|
||||
parent::__construct($response, $template);
|
||||
$this->navigation = $navigation;
|
||||
$this->relation = $relation;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
if (! $this->response->isAjax()) {
|
||||
$this->response->addHTML(
|
||||
Message::error(
|
||||
__('Fatal error: The navigation can only be accessed via AJAX')
|
||||
)->getDisplay()
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($_POST['getNaviSettings']) && $_POST['getNaviSettings']) {
|
||||
$pageSettings = new PageSettings('Navi', 'pma_navigation_settings');
|
||||
$this->response->addHTML($pageSettings->getErrorHTML());
|
||||
$this->response->addJSON('message', $pageSettings->getHTML());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($_POST['reload'])) {
|
||||
SessionCache::set('dbs_to_test', false);// Empty database list cache, see #14252
|
||||
}
|
||||
|
||||
$cfgRelation = $this->relation->getRelationsParam();
|
||||
if ($cfgRelation['navwork']) {
|
||||
if (isset($_POST['hideNavItem'])) {
|
||||
if (! empty($_POST['itemName'])
|
||||
&& ! empty($_POST['itemType'])
|
||||
&& ! empty($_POST['dbName'])
|
||||
) {
|
||||
$this->navigation->hideNavigationItem(
|
||||
$_POST['itemName'],
|
||||
$_POST['itemType'],
|
||||
$_POST['dbName'],
|
||||
(! empty($_POST['tableName']) ? $_POST['tableName'] : null)
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($_POST['unhideNavItem'])) {
|
||||
if (! empty($_POST['itemName'])
|
||||
&& ! empty($_POST['itemType'])
|
||||
&& ! empty($_POST['dbName'])
|
||||
) {
|
||||
$this->navigation->unhideNavigationItem(
|
||||
$_POST['itemName'],
|
||||
$_POST['itemType'],
|
||||
$_POST['dbName'],
|
||||
(! empty($_POST['tableName']) ? $_POST['tableName'] : null)
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($_POST['showUnhideDialog'])) {
|
||||
if (! empty($_POST['dbName'])) {
|
||||
$this->response->addJSON(
|
||||
'message',
|
||||
$this->navigation->getItemUnhideDialog($_POST['dbName'])
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$this->response->addJSON('message', $this->navigation->getDisplay());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,163 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers;
|
||||
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\Normalization;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use function intval;
|
||||
use function json_decode;
|
||||
use function json_encode;
|
||||
use function min;
|
||||
|
||||
/**
|
||||
* Normalization process (temporarily specific to 1NF).
|
||||
*/
|
||||
class NormalizationController extends AbstractController
|
||||
{
|
||||
/** @var Normalization */
|
||||
private $normalization;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
*/
|
||||
public function __construct($response, Template $template, Normalization $normalization)
|
||||
{
|
||||
parent::__construct($response, $template);
|
||||
$this->normalization = $normalization;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $db, $table;
|
||||
|
||||
if (isset($_POST['getColumns'])) {
|
||||
$html = '<option selected disabled>' . __('Select one…') . '</option>'
|
||||
. '<option value="no_such_col">' . __('No such column') . '</option>';
|
||||
//get column whose datatype falls under string category
|
||||
$html .= $this->normalization->getHtmlForColumnsList(
|
||||
$db,
|
||||
$table,
|
||||
_pgettext('string types', 'String')
|
||||
);
|
||||
echo $html;
|
||||
|
||||
return;
|
||||
}
|
||||
if (isset($_POST['splitColumn'])) {
|
||||
$num_fields = min(4096, intval($_POST['numFields']));
|
||||
$html = $this->normalization->getHtmlForCreateNewColumn($num_fields, $db, $table);
|
||||
$html .= Url::getHiddenInputs($db, $table);
|
||||
echo $html;
|
||||
|
||||
return;
|
||||
}
|
||||
if (isset($_POST['addNewPrimary'])) {
|
||||
$num_fields = 1;
|
||||
$columnMeta = [
|
||||
'Field' => $table . '_id',
|
||||
'Extra' => 'auto_increment',
|
||||
];
|
||||
$html = $this->normalization->getHtmlForCreateNewColumn(
|
||||
$num_fields,
|
||||
$db,
|
||||
$table,
|
||||
$columnMeta
|
||||
);
|
||||
$html .= Url::getHiddenInputs($db, $table);
|
||||
echo $html;
|
||||
|
||||
return;
|
||||
}
|
||||
if (isset($_POST['findPdl'])) {
|
||||
$html = $this->normalization->findPartialDependencies($table, $db);
|
||||
echo $html;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($_POST['getNewTables2NF'])) {
|
||||
$partialDependencies = json_decode($_POST['pd'], true);
|
||||
$html = $this->normalization->getHtmlForNewTables2NF($partialDependencies, $table);
|
||||
echo $html;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($_POST['getNewTables3NF'])) {
|
||||
$dependencies = json_decode($_POST['pd']);
|
||||
$tables = json_decode($_POST['tables'], true);
|
||||
$newTables = $this->normalization->getHtmlForNewTables3NF($dependencies, $tables, $db);
|
||||
$this->response->disable();
|
||||
Core::headerJSON();
|
||||
echo json_encode($newTables);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->addScriptFiles(['normalization.js', 'vendor/jquery/jquery.uitablefilter.js']);
|
||||
|
||||
$normalForm = '1nf';
|
||||
if (Core::isValid($_POST['normalizeTo'], ['1nf', '2nf', '3nf'])) {
|
||||
$normalForm = $_POST['normalizeTo'];
|
||||
}
|
||||
if (isset($_POST['createNewTables2NF'])) {
|
||||
$partialDependencies = json_decode($_POST['pd'], true);
|
||||
$tablesName = json_decode($_POST['newTablesName']);
|
||||
$res = $this->normalization->createNewTablesFor2NF($partialDependencies, $tablesName, $table, $db);
|
||||
$this->response->addJSON($res);
|
||||
|
||||
return;
|
||||
}
|
||||
if (isset($_POST['createNewTables3NF'])) {
|
||||
$newtables = json_decode($_POST['newTables'], true);
|
||||
$res = $this->normalization->createNewTablesFor3NF($newtables, $db);
|
||||
$this->response->addJSON($res);
|
||||
|
||||
return;
|
||||
}
|
||||
if (isset($_POST['repeatingColumns'])) {
|
||||
$repeatingColumns = $_POST['repeatingColumns'];
|
||||
$newTable = $_POST['newTable'];
|
||||
$newColumn = $_POST['newColumn'];
|
||||
$primary_columns = $_POST['primary_columns'];
|
||||
$res = $this->normalization->moveRepeatingGroup(
|
||||
$repeatingColumns,
|
||||
$primary_columns,
|
||||
$newTable,
|
||||
$newColumn,
|
||||
$table,
|
||||
$db
|
||||
);
|
||||
$this->response->addJSON($res);
|
||||
|
||||
return;
|
||||
}
|
||||
if (isset($_POST['step1'])) {
|
||||
$html = $this->normalization->getHtmlFor1NFStep1($db, $table, $normalForm);
|
||||
$this->response->addHTML($html);
|
||||
} elseif (isset($_POST['step2'])) {
|
||||
$res = $this->normalization->getHtmlContentsFor1NFStep2($db, $table);
|
||||
$this->response->addJSON($res);
|
||||
} elseif (isset($_POST['step3'])) {
|
||||
$res = $this->normalization->getHtmlContentsFor1NFStep3($db, $table);
|
||||
$this->response->addJSON($res);
|
||||
} elseif (isset($_POST['step4'])) {
|
||||
$res = $this->normalization->getHtmlContentsFor1NFStep4($db, $table);
|
||||
$this->response->addJSON($res);
|
||||
} elseif (isset($_POST['step']) && $_POST['step'] == '2.1') {
|
||||
$res = $this->normalization->getHtmlFor2NFstep1($db, $table);
|
||||
$this->response->addJSON($res);
|
||||
} elseif (isset($_POST['step']) && $_POST['step'] == '3.1') {
|
||||
$tables = $_POST['tables'];
|
||||
$res = $this->normalization->getHtmlFor3NFstep1($db, $tables);
|
||||
$this->response->addJSON($res);
|
||||
} else {
|
||||
$this->response->addHTML($this->normalization->getHtmlForNormalizeTable());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
/**
|
||||
* phpinfo() wrapper to allow displaying only when configured to do so.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers;
|
||||
|
||||
use const INFO_CONFIGURATION;
|
||||
use const INFO_GENERAL;
|
||||
use const INFO_MODULES;
|
||||
use function phpinfo;
|
||||
|
||||
/**
|
||||
* phpinfo() wrapper to allow displaying only when configured to do so.
|
||||
*/
|
||||
class PhpInfoController extends AbstractController
|
||||
{
|
||||
public function index(): void
|
||||
{
|
||||
global $cfg;
|
||||
|
||||
$this->response->disable();
|
||||
$this->response->getHeader()->sendHttpHeaders();
|
||||
|
||||
if (! $cfg['ShowPhpInfo']) {
|
||||
return;
|
||||
}
|
||||
|
||||
phpinfo(INFO_GENERAL | INFO_CONFIGURATION | INFO_MODULES);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Preferences;
|
||||
|
||||
use PhpMyAdmin\Config\ConfigFile;
|
||||
use PhpMyAdmin\Config\Forms\User\ExportForm;
|
||||
use PhpMyAdmin\Controllers\AbstractController;
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\Relation;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\TwoFactor;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\UserPreferences;
|
||||
use function define;
|
||||
use function ltrim;
|
||||
|
||||
class ExportController extends AbstractController
|
||||
{
|
||||
/** @var UserPreferences */
|
||||
private $userPreferences;
|
||||
|
||||
/** @var Relation */
|
||||
private $relation;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
*/
|
||||
public function __construct(
|
||||
$response,
|
||||
Template $template,
|
||||
UserPreferences $userPreferences,
|
||||
Relation $relation
|
||||
) {
|
||||
parent::__construct($response, $template);
|
||||
$this->userPreferences = $userPreferences;
|
||||
$this->relation = $relation;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $cfg, $cf, $error, $tabHash, $hash;
|
||||
global $server, $PMA_Config, $route;
|
||||
|
||||
$cf = new ConfigFile($PMA_Config->baseSettings);
|
||||
$this->userPreferences->pageInit($cf);
|
||||
|
||||
$formDisplay = new ExportForm($cf, 1);
|
||||
|
||||
if (isset($_POST['revert'])) {
|
||||
// revert erroneous fields to their default values
|
||||
$formDisplay->fixErrors();
|
||||
Core::sendHeaderLocation('./index.php?route=/preferences/export');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$error = null;
|
||||
if ($formDisplay->process(false) && ! $formDisplay->hasErrors()) {
|
||||
// Load 2FA settings
|
||||
$twoFactor = new TwoFactor($cfg['Server']['user']);
|
||||
// save settings
|
||||
$result = $this->userPreferences->save($cf->getConfigArray());
|
||||
// save back the 2FA setting only
|
||||
$twoFactor->save();
|
||||
if ($result === true) {
|
||||
// reload config
|
||||
$PMA_Config->loadUserPreferences();
|
||||
$tabHash = $_POST['tab_hash'] ?? null;
|
||||
$hash = ltrim($tabHash, '#');
|
||||
$this->userPreferences->redirect(
|
||||
'index.php?route=/preferences/export',
|
||||
null,
|
||||
$hash
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$error = $result;
|
||||
}
|
||||
|
||||
$this->addScriptFiles(['config.js']);
|
||||
|
||||
$cfgRelation = $this->relation->getRelationsParam();
|
||||
|
||||
$this->render('preferences/header', [
|
||||
'route' => $route,
|
||||
'is_saved' => ! empty($_GET['saved']),
|
||||
'has_config_storage' => $cfgRelation['userconfigwork'],
|
||||
]);
|
||||
|
||||
if ($formDisplay->hasErrors()) {
|
||||
$formErrors = $formDisplay->displayErrors();
|
||||
}
|
||||
|
||||
$this->render('preferences/forms/main', [
|
||||
'error' => $error ? $error->getDisplay() : '',
|
||||
'has_errors' => $formDisplay->hasErrors(),
|
||||
'errors' => $formErrors ?? null,
|
||||
'form' => $formDisplay->getDisplay(
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
Url::getFromRoute('/preferences/export'),
|
||||
['server' => $server]
|
||||
),
|
||||
]);
|
||||
|
||||
if ($this->response->isAjax()) {
|
||||
$this->response->addJSON('disableNaviSettings', true);
|
||||
} else {
|
||||
define('PMA_DISABLE_NAVI_SETTINGS', true);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Preferences;
|
||||
|
||||
use PhpMyAdmin\Config\ConfigFile;
|
||||
use PhpMyAdmin\Config\Forms\User\FeaturesForm;
|
||||
use PhpMyAdmin\Controllers\AbstractController;
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\Relation;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\TwoFactor;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\UserPreferences;
|
||||
use function define;
|
||||
use function ltrim;
|
||||
|
||||
class FeaturesController extends AbstractController
|
||||
{
|
||||
/** @var UserPreferences */
|
||||
private $userPreferences;
|
||||
|
||||
/** @var Relation */
|
||||
private $relation;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
*/
|
||||
public function __construct(
|
||||
$response,
|
||||
Template $template,
|
||||
UserPreferences $userPreferences,
|
||||
Relation $relation
|
||||
) {
|
||||
parent::__construct($response, $template);
|
||||
$this->userPreferences = $userPreferences;
|
||||
$this->relation = $relation;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $cfg, $cf, $error, $tabHash, $hash;
|
||||
global $server, $PMA_Config, $route;
|
||||
|
||||
$cf = new ConfigFile($PMA_Config->baseSettings);
|
||||
$this->userPreferences->pageInit($cf);
|
||||
|
||||
$formDisplay = new FeaturesForm($cf, 1);
|
||||
|
||||
if (isset($_POST['revert'])) {
|
||||
// revert erroneous fields to their default values
|
||||
$formDisplay->fixErrors();
|
||||
Core::sendHeaderLocation('./index.php?route=/preferences/features');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$error = null;
|
||||
if ($formDisplay->process(false) && ! $formDisplay->hasErrors()) {
|
||||
// Load 2FA settings
|
||||
$twoFactor = new TwoFactor($cfg['Server']['user']);
|
||||
// save settings
|
||||
$result = $this->userPreferences->save($cf->getConfigArray());
|
||||
// save back the 2FA setting only
|
||||
$twoFactor->save();
|
||||
if ($result === true) {
|
||||
// reload config
|
||||
$PMA_Config->loadUserPreferences();
|
||||
$tabHash = $_POST['tab_hash'] ?? null;
|
||||
$hash = ltrim($tabHash, '#');
|
||||
$this->userPreferences->redirect(
|
||||
'index.php?route=/preferences/features',
|
||||
null,
|
||||
$hash
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$error = $result;
|
||||
}
|
||||
|
||||
$this->addScriptFiles(['config.js']);
|
||||
|
||||
$cfgRelation = $this->relation->getRelationsParam();
|
||||
|
||||
$this->render('preferences/header', [
|
||||
'route' => $route,
|
||||
'is_saved' => ! empty($_GET['saved']),
|
||||
'has_config_storage' => $cfgRelation['userconfigwork'],
|
||||
]);
|
||||
|
||||
if ($formDisplay->hasErrors()) {
|
||||
$formErrors = $formDisplay->displayErrors();
|
||||
}
|
||||
|
||||
$this->render('preferences/forms/main', [
|
||||
'error' => $error ? $error->getDisplay() : '',
|
||||
'has_errors' => $formDisplay->hasErrors(),
|
||||
'errors' => $formErrors ?? null,
|
||||
'form' => $formDisplay->getDisplay(
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
Url::getFromRoute('/preferences/features'),
|
||||
['server' => $server]
|
||||
),
|
||||
]);
|
||||
|
||||
if ($this->response->isAjax()) {
|
||||
$this->response->addJSON('disableNaviSettings', true);
|
||||
} else {
|
||||
define('PMA_DISABLE_NAVI_SETTINGS', true);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Preferences;
|
||||
|
||||
use PhpMyAdmin\Config\ConfigFile;
|
||||
use PhpMyAdmin\Config\Forms\User\ImportForm;
|
||||
use PhpMyAdmin\Controllers\AbstractController;
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\Relation;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\TwoFactor;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\UserPreferences;
|
||||
use function define;
|
||||
use function ltrim;
|
||||
|
||||
class ImportController extends AbstractController
|
||||
{
|
||||
/** @var UserPreferences */
|
||||
private $userPreferences;
|
||||
|
||||
/** @var Relation */
|
||||
private $relation;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
*/
|
||||
public function __construct(
|
||||
$response,
|
||||
Template $template,
|
||||
UserPreferences $userPreferences,
|
||||
Relation $relation
|
||||
) {
|
||||
parent::__construct($response, $template);
|
||||
$this->userPreferences = $userPreferences;
|
||||
$this->relation = $relation;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $cfg, $cf, $error, $tabHash, $hash;
|
||||
global $server, $PMA_Config, $route;
|
||||
|
||||
$cf = new ConfigFile($PMA_Config->baseSettings);
|
||||
$this->userPreferences->pageInit($cf);
|
||||
|
||||
$formDisplay = new ImportForm($cf, 1);
|
||||
|
||||
if (isset($_POST['revert'])) {
|
||||
// revert erroneous fields to their default values
|
||||
$formDisplay->fixErrors();
|
||||
Core::sendHeaderLocation('./index.php?route=/preferences/import');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$error = null;
|
||||
if ($formDisplay->process(false) && ! $formDisplay->hasErrors()) {
|
||||
// Load 2FA settings
|
||||
$twoFactor = new TwoFactor($cfg['Server']['user']);
|
||||
// save settings
|
||||
$result = $this->userPreferences->save($cf->getConfigArray());
|
||||
// save back the 2FA setting only
|
||||
$twoFactor->save();
|
||||
if ($result === true) {
|
||||
// reload config
|
||||
$PMA_Config->loadUserPreferences();
|
||||
$tabHash = $_POST['tab_hash'] ?? null;
|
||||
$hash = ltrim($tabHash, '#');
|
||||
$this->userPreferences->redirect(
|
||||
'index.php?route=/preferences/import',
|
||||
null,
|
||||
$hash
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$error = $result;
|
||||
}
|
||||
|
||||
$this->addScriptFiles(['config.js']);
|
||||
|
||||
$cfgRelation = $this->relation->getRelationsParam();
|
||||
|
||||
$this->render('preferences/header', [
|
||||
'route' => $route,
|
||||
'is_saved' => ! empty($_GET['saved']),
|
||||
'has_config_storage' => $cfgRelation['userconfigwork'],
|
||||
]);
|
||||
|
||||
if ($formDisplay->hasErrors()) {
|
||||
$formErrors = $formDisplay->displayErrors();
|
||||
}
|
||||
|
||||
$this->render('preferences/forms/main', [
|
||||
'error' => $error ? $error->getDisplay() : '',
|
||||
'has_errors' => $formDisplay->hasErrors(),
|
||||
'errors' => $formErrors ?? null,
|
||||
'form' => $formDisplay->getDisplay(
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
Url::getFromRoute('/preferences/import'),
|
||||
['server' => $server]
|
||||
),
|
||||
]);
|
||||
|
||||
if ($this->response->isAjax()) {
|
||||
$this->response->addJSON('disableNaviSettings', true);
|
||||
} else {
|
||||
define('PMA_DISABLE_NAVI_SETTINGS', true);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Preferences;
|
||||
|
||||
use PhpMyAdmin\Config\ConfigFile;
|
||||
use PhpMyAdmin\Config\Forms\User\MainForm;
|
||||
use PhpMyAdmin\Controllers\AbstractController;
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\Relation;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\TwoFactor;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\UserPreferences;
|
||||
use function define;
|
||||
use function ltrim;
|
||||
|
||||
class MainPanelController extends AbstractController
|
||||
{
|
||||
/** @var UserPreferences */
|
||||
private $userPreferences;
|
||||
|
||||
/** @var Relation */
|
||||
private $relation;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
*/
|
||||
public function __construct(
|
||||
$response,
|
||||
Template $template,
|
||||
UserPreferences $userPreferences,
|
||||
Relation $relation
|
||||
) {
|
||||
parent::__construct($response, $template);
|
||||
$this->userPreferences = $userPreferences;
|
||||
$this->relation = $relation;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $cfg, $cf, $error, $tabHash, $hash;
|
||||
global $server, $PMA_Config, $route;
|
||||
|
||||
$cf = new ConfigFile($PMA_Config->baseSettings);
|
||||
$this->userPreferences->pageInit($cf);
|
||||
|
||||
$formDisplay = new MainForm($cf, 1);
|
||||
|
||||
if (isset($_POST['revert'])) {
|
||||
// revert erroneous fields to their default values
|
||||
$formDisplay->fixErrors();
|
||||
Core::sendHeaderLocation('./index.php?route=/preferences/main-panel');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$error = null;
|
||||
if ($formDisplay->process(false) && ! $formDisplay->hasErrors()) {
|
||||
// Load 2FA settings
|
||||
$twoFactor = new TwoFactor($cfg['Server']['user']);
|
||||
// save settings
|
||||
$result = $this->userPreferences->save($cf->getConfigArray());
|
||||
// save back the 2FA setting only
|
||||
$twoFactor->save();
|
||||
if ($result === true) {
|
||||
// reload config
|
||||
$PMA_Config->loadUserPreferences();
|
||||
$tabHash = $_POST['tab_hash'] ?? null;
|
||||
$hash = ltrim($tabHash, '#');
|
||||
$this->userPreferences->redirect(
|
||||
'index.php?route=/preferences/main-panel',
|
||||
null,
|
||||
$hash
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$error = $result;
|
||||
}
|
||||
|
||||
$this->addScriptFiles(['config.js']);
|
||||
|
||||
$cfgRelation = $this->relation->getRelationsParam();
|
||||
|
||||
$this->render('preferences/header', [
|
||||
'route' => $route,
|
||||
'is_saved' => ! empty($_GET['saved']),
|
||||
'has_config_storage' => $cfgRelation['userconfigwork'],
|
||||
]);
|
||||
|
||||
if ($formDisplay->hasErrors()) {
|
||||
$formErrors = $formDisplay->displayErrors();
|
||||
}
|
||||
|
||||
$this->render('preferences/forms/main', [
|
||||
'error' => $error ? $error->getDisplay() : '',
|
||||
'has_errors' => $formDisplay->hasErrors(),
|
||||
'errors' => $formErrors ?? null,
|
||||
'form' => $formDisplay->getDisplay(
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
Url::getFromRoute('/preferences/main-panel'),
|
||||
['server' => $server]
|
||||
),
|
||||
]);
|
||||
|
||||
if ($this->response->isAjax()) {
|
||||
$this->response->addJSON('disableNaviSettings', true);
|
||||
} else {
|
||||
define('PMA_DISABLE_NAVI_SETTINGS', true);
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue