Update website
This commit is contained in:
parent
4413528994
commit
1d90fbf296
6865 changed files with 1091082 additions and 0 deletions
718
admin/phpMyAdmin/libraries/advisory_rules_generic.php
Normal file
718
admin/phpMyAdmin/libraries/advisory_rules_generic.php
Normal file
|
@ -0,0 +1,718 @@
|
|||
<?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 replicas.'
|
||||
),
|
||||
'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 && ! (IS_MARIADB && PMA_MYSQL_INT_VERSION > 100500)',
|
||||
'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' => 'InnoDB log size',
|
||||
'name' => __('InnoDB log size'),
|
||||
'precondition' => 'innodb_buffer_pool_size > 0 && IS_MARIADB && PMA_MYSQL_INT_VERSION > 100500',
|
||||
// From MariaDB 10.5, there is 1 redo log.
|
||||
// For MariaDB 10.4 and before, the number of redo log files is configured
|
||||
// by the innodb_log_files_in_group system variable.
|
||||
'formula' => 'innodb_log_file_size / 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'),
|
||||
],
|
||||
];
|
326
admin/phpMyAdmin/libraries/cache/routes.cache.php
vendored
Normal file
326
admin/phpMyAdmin/libraries/cache/routes.cache.php
vendored
Normal file
|
@ -0,0 +1,326 @@
|
|||
<?php return array (
|
||||
0 =>
|
||||
array (
|
||||
'GET' =>
|
||||
array (
|
||||
'' => 'PhpMyAdmin\\Controllers\\HomeController',
|
||||
'/' => 'PhpMyAdmin\\Controllers\\HomeController',
|
||||
'/browse-foreigners' => 'PhpMyAdmin\\Controllers\\BrowseForeignersController',
|
||||
'/changelog' => 'PhpMyAdmin\\Controllers\\ChangeLogController',
|
||||
'/check-relations' => 'PhpMyAdmin\\Controllers\\CheckRelationsController',
|
||||
'/database/central-columns' => 'PhpMyAdmin\\Controllers\\Database\\CentralColumnsController',
|
||||
'/database/data-dictionary' => 'PhpMyAdmin\\Controllers\\Database\\DataDictionaryController',
|
||||
'/database/designer' => 'PhpMyAdmin\\Controllers\\Database\\DesignerController',
|
||||
'/database/events' => 'PhpMyAdmin\\Controllers\\Database\\EventsController',
|
||||
'/database/export' => 'PhpMyAdmin\\Controllers\\Database\\ExportController',
|
||||
'/database/import' => 'PhpMyAdmin\\Controllers\\Database\\ImportController',
|
||||
'/database/multi-table-query' => 'PhpMyAdmin\\Controllers\\Database\\MultiTableQueryController',
|
||||
'/database/multi-table-query/tables' => 'PhpMyAdmin\\Controllers\\Database\\MultiTableQuery\\TablesController',
|
||||
'/database/operations' => 'PhpMyAdmin\\Controllers\\Database\\OperationsController',
|
||||
'/database/qbe' => 'PhpMyAdmin\\Controllers\\Database\\QueryByExampleController',
|
||||
'/database/routines' => 'PhpMyAdmin\\Controllers\\Database\\RoutinesController',
|
||||
'/database/search' => 'PhpMyAdmin\\Controllers\\Database\\SearchController',
|
||||
'/database/sql' => 'PhpMyAdmin\\Controllers\\Database\\SqlController',
|
||||
'/database/structure' => 'PhpMyAdmin\\Controllers\\Database\\StructureController',
|
||||
'/database/structure/real-row-count' => 'PhpMyAdmin\\Controllers\\Database\\Structure\\RealRowCountController',
|
||||
'/database/tracking' => 'PhpMyAdmin\\Controllers\\Database\\TrackingController',
|
||||
'/database/triggers' => 'PhpMyAdmin\\Controllers\\Database\\TriggersController',
|
||||
'/error-report' => 'PhpMyAdmin\\Controllers\\ErrorReportController',
|
||||
'/export' => 'PhpMyAdmin\\Controllers\\Export\\ExportController',
|
||||
'/export/check-time-out' => 'PhpMyAdmin\\Controllers\\Export\\CheckTimeOutController',
|
||||
'/gis-data-editor' => 'PhpMyAdmin\\Controllers\\GisDataEditorController',
|
||||
'/git-revision' => 'PhpMyAdmin\\Controllers\\GitInfoController',
|
||||
'/import' => 'PhpMyAdmin\\Controllers\\Import\\ImportController',
|
||||
'/import-status' => 'PhpMyAdmin\\Controllers\\Import\\StatusController',
|
||||
'/license' => 'PhpMyAdmin\\Controllers\\LicenseController',
|
||||
'/lint' => 'PhpMyAdmin\\Controllers\\LintController',
|
||||
'/logout' => 'PhpMyAdmin\\Controllers\\LogoutController',
|
||||
'/navigation' => 'PhpMyAdmin\\Controllers\\NavigationController',
|
||||
'/normalization' => 'PhpMyAdmin\\Controllers\\NormalizationController',
|
||||
'/phpinfo' => 'PhpMyAdmin\\Controllers\\PhpInfoController',
|
||||
'/preferences/export' => 'PhpMyAdmin\\Controllers\\Preferences\\ExportController',
|
||||
'/preferences/features' => 'PhpMyAdmin\\Controllers\\Preferences\\FeaturesController',
|
||||
'/preferences/import' => 'PhpMyAdmin\\Controllers\\Preferences\\ImportController',
|
||||
'/preferences/main-panel' => 'PhpMyAdmin\\Controllers\\Preferences\\MainPanelController',
|
||||
'/preferences/manage' => 'PhpMyAdmin\\Controllers\\Preferences\\ManageController',
|
||||
'/preferences/navigation' => 'PhpMyAdmin\\Controllers\\Preferences\\NavigationController',
|
||||
'/preferences/sql' => 'PhpMyAdmin\\Controllers\\Preferences\\SqlController',
|
||||
'/preferences/two-factor' => 'PhpMyAdmin\\Controllers\\Preferences\\TwoFactorController',
|
||||
'/recent-table' => 'PhpMyAdmin\\Controllers\\RecentTablesListController',
|
||||
'/schema-export' => 'PhpMyAdmin\\Controllers\\SchemaExportController',
|
||||
'/server/binlog' => 'PhpMyAdmin\\Controllers\\Server\\BinlogController',
|
||||
'/server/collations' => 'PhpMyAdmin\\Controllers\\Server\\CollationsController',
|
||||
'/server/databases' => 'PhpMyAdmin\\Controllers\\Server\\DatabasesController',
|
||||
'/server/engines' => 'PhpMyAdmin\\Controllers\\Server\\EnginesController',
|
||||
'/server/export' => 'PhpMyAdmin\\Controllers\\Server\\ExportController',
|
||||
'/server/import' => 'PhpMyAdmin\\Controllers\\Server\\ImportController',
|
||||
'/server/plugins' => 'PhpMyAdmin\\Controllers\\Server\\PluginsController',
|
||||
'/server/privileges' => 'PhpMyAdmin\\Controllers\\Server\\PrivilegesController',
|
||||
'/server/replication' => 'PhpMyAdmin\\Controllers\\Server\\ReplicationController',
|
||||
'/server/sql' => 'PhpMyAdmin\\Controllers\\Server\\SqlController',
|
||||
'/server/status' => 'PhpMyAdmin\\Controllers\\Server\\Status\\StatusController',
|
||||
'/server/status/advisor' => 'PhpMyAdmin\\Controllers\\Server\\Status\\AdvisorController',
|
||||
'/server/status/monitor' => 'PhpMyAdmin\\Controllers\\Server\\Status\\MonitorController',
|
||||
'/server/status/processes' => 'PhpMyAdmin\\Controllers\\Server\\Status\\ProcessesController',
|
||||
'/server/status/queries' => 'PhpMyAdmin\\Controllers\\Server\\Status\\QueriesController',
|
||||
'/server/status/variables' => 'PhpMyAdmin\\Controllers\\Server\\Status\\VariablesController',
|
||||
'/server/user-groups' => 'PhpMyAdmin\\Controllers\\Server\\UserGroupsController',
|
||||
'/server/user-groups/edit-form' => 'PhpMyAdmin\\Controllers\\Server\\UserGroupsFormController',
|
||||
'/server/variables' => 'PhpMyAdmin\\Controllers\\Server\\VariablesController',
|
||||
'/sql' => 'PhpMyAdmin\\Controllers\\Sql\\SqlController',
|
||||
'/sql/get-default-fk-check-value' => 'PhpMyAdmin\\Controllers\\Sql\\DefaultForeignKeyCheckValueController',
|
||||
'/table/add-field' => 'PhpMyAdmin\\Controllers\\Table\\AddFieldController',
|
||||
'/table/change' => 'PhpMyAdmin\\Controllers\\Table\\ChangeController',
|
||||
'/table/chart' => 'PhpMyAdmin\\Controllers\\Table\\ChartController',
|
||||
'/table/create' => 'PhpMyAdmin\\Controllers\\Table\\CreateController',
|
||||
'/table/export' => 'PhpMyAdmin\\Controllers\\Table\\ExportController',
|
||||
'/table/find-replace' => 'PhpMyAdmin\\Controllers\\Table\\FindReplaceController',
|
||||
'/table/get-field' => 'PhpMyAdmin\\Controllers\\Table\\GetFieldController',
|
||||
'/table/gis-visualization' => 'PhpMyAdmin\\Controllers\\Table\\GisVisualizationController',
|
||||
'/table/import' => 'PhpMyAdmin\\Controllers\\Table\\ImportController',
|
||||
'/table/indexes' => 'PhpMyAdmin\\Controllers\\Table\\IndexesController',
|
||||
'/table/indexes/rename' => 'PhpMyAdmin\\Controllers\\Table\\IndexRenameController',
|
||||
'/table/operations' => 'PhpMyAdmin\\Controllers\\Table\\OperationsController',
|
||||
'/table/recent-favorite' => 'PhpMyAdmin\\Controllers\\Table\\RecentFavoriteController',
|
||||
'/table/relation' => 'PhpMyAdmin\\Controllers\\Table\\RelationController',
|
||||
'/table/replace' => 'PhpMyAdmin\\Controllers\\Table\\ReplaceController',
|
||||
'/table/search' => 'PhpMyAdmin\\Controllers\\Table\\SearchController',
|
||||
'/table/sql' => 'PhpMyAdmin\\Controllers\\Table\\SqlController',
|
||||
'/table/structure' => 'PhpMyAdmin\\Controllers\\Table\\StructureController',
|
||||
'/table/structure/change' => 'PhpMyAdmin\\Controllers\\Table\\Structure\\ChangeController',
|
||||
'/table/tracking' => 'PhpMyAdmin\\Controllers\\Table\\TrackingController',
|
||||
'/table/triggers' => 'PhpMyAdmin\\Controllers\\Table\\TriggersController',
|
||||
'/table/zoom-search' => 'PhpMyAdmin\\Controllers\\Table\\ZoomSearchController',
|
||||
'/themes' => 'PhpMyAdmin\\Controllers\\ThemesController',
|
||||
'/transformation/overview' => 'PhpMyAdmin\\Controllers\\Transformation\\OverviewController',
|
||||
'/transformation/wrapper' => 'PhpMyAdmin\\Controllers\\Transformation\\WrapperController',
|
||||
'/user-password' => 'PhpMyAdmin\\Controllers\\UserPasswordController',
|
||||
'/version-check' => 'PhpMyAdmin\\Controllers\\VersionCheckController',
|
||||
'/view/create' => 'PhpMyAdmin\\Controllers\\View\\CreateController',
|
||||
'/view/operations' => 'PhpMyAdmin\\Controllers\\View\\OperationsController',
|
||||
),
|
||||
'POST' =>
|
||||
array (
|
||||
'' => 'PhpMyAdmin\\Controllers\\HomeController',
|
||||
'/' => 'PhpMyAdmin\\Controllers\\HomeController',
|
||||
'/browse-foreigners' => 'PhpMyAdmin\\Controllers\\BrowseForeignersController',
|
||||
'/check-relations' => 'PhpMyAdmin\\Controllers\\CheckRelationsController',
|
||||
'/collation-connection' => 'PhpMyAdmin\\Controllers\\CollationConnectionController',
|
||||
'/columns' => 'PhpMyAdmin\\Controllers\\ColumnController',
|
||||
'/config/get' => 'PhpMyAdmin\\Controllers\\Config\\GetConfigController',
|
||||
'/config/set' => 'PhpMyAdmin\\Controllers\\Config\\SetConfigController',
|
||||
'/database/central-columns' => 'PhpMyAdmin\\Controllers\\Database\\CentralColumnsController',
|
||||
'/database/central-columns/populate' => 'PhpMyAdmin\\Controllers\\Database\\CentralColumns\\PopulateColumnsController',
|
||||
'/database/designer' => 'PhpMyAdmin\\Controllers\\Database\\DesignerController',
|
||||
'/database/events' => 'PhpMyAdmin\\Controllers\\Database\\EventsController',
|
||||
'/database/export' => 'PhpMyAdmin\\Controllers\\Database\\ExportController',
|
||||
'/database/import' => 'PhpMyAdmin\\Controllers\\Database\\ImportController',
|
||||
'/database/multi-table-query/query' => 'PhpMyAdmin\\Controllers\\Database\\MultiTableQuery\\QueryController',
|
||||
'/database/operations' => 'PhpMyAdmin\\Controllers\\Database\\OperationsController',
|
||||
'/database/operations/collation' => 'PhpMyAdmin\\Controllers\\Database\\Operations\\CollationController',
|
||||
'/database/qbe' => 'PhpMyAdmin\\Controllers\\Database\\QueryByExampleController',
|
||||
'/database/routines' => 'PhpMyAdmin\\Controllers\\Database\\RoutinesController',
|
||||
'/database/search' => 'PhpMyAdmin\\Controllers\\Database\\SearchController',
|
||||
'/database/sql' => 'PhpMyAdmin\\Controllers\\Database\\SqlController',
|
||||
'/database/sql/autocomplete' => 'PhpMyAdmin\\Controllers\\Database\\SqlAutoCompleteController',
|
||||
'/database/sql/format' => 'PhpMyAdmin\\Controllers\\Database\\SqlFormatController',
|
||||
'/database/structure' => 'PhpMyAdmin\\Controllers\\Database\\StructureController',
|
||||
'/database/structure/add-prefix' => 'PhpMyAdmin\\Controllers\\Database\\Structure\\AddPrefixController',
|
||||
'/database/structure/add-prefix-table' => 'PhpMyAdmin\\Controllers\\Database\\Structure\\AddPrefixTableController',
|
||||
'/database/structure/central-columns/add' => 'PhpMyAdmin\\Controllers\\Database\\Structure\\CentralColumns\\AddController',
|
||||
'/database/structure/central-columns/make-consistent' => 'PhpMyAdmin\\Controllers\\Database\\Structure\\CentralColumns\\MakeConsistentController',
|
||||
'/database/structure/central-columns/remove' => 'PhpMyAdmin\\Controllers\\Database\\Structure\\CentralColumns\\RemoveController',
|
||||
'/database/structure/change-prefix-form' => 'PhpMyAdmin\\Controllers\\Database\\Structure\\ChangePrefixFormController',
|
||||
'/database/structure/copy-form' => 'PhpMyAdmin\\Controllers\\Database\\Structure\\CopyFormController',
|
||||
'/database/structure/copy-table' => 'PhpMyAdmin\\Controllers\\Database\\Structure\\CopyTableController',
|
||||
'/database/structure/copy-table-with-prefix' => 'PhpMyAdmin\\Controllers\\Database\\Structure\\CopyTableWithPrefixController',
|
||||
'/database/structure/drop-form' => 'PhpMyAdmin\\Controllers\\Database\\Structure\\DropFormController',
|
||||
'/database/structure/drop-table' => 'PhpMyAdmin\\Controllers\\Database\\Structure\\DropTableController',
|
||||
'/database/structure/empty-form' => 'PhpMyAdmin\\Controllers\\Database\\Structure\\EmptyFormController',
|
||||
'/database/structure/empty-table' => 'PhpMyAdmin\\Controllers\\Database\\Structure\\EmptyTableController',
|
||||
'/database/structure/favorite-table' => 'PhpMyAdmin\\Controllers\\Database\\Structure\\FavoriteTableController',
|
||||
'/database/structure/real-row-count' => 'PhpMyAdmin\\Controllers\\Database\\Structure\\RealRowCountController',
|
||||
'/database/structure/replace-prefix' => 'PhpMyAdmin\\Controllers\\Database\\Structure\\ReplacePrefixController',
|
||||
'/database/structure/show-create' => 'PhpMyAdmin\\Controllers\\Database\\Structure\\ShowCreateController',
|
||||
'/database/tracking' => 'PhpMyAdmin\\Controllers\\Database\\TrackingController',
|
||||
'/database/triggers' => 'PhpMyAdmin\\Controllers\\Database\\TriggersController',
|
||||
'/databases' => 'PhpMyAdmin\\Controllers\\DatabaseController',
|
||||
'/error-report' => 'PhpMyAdmin\\Controllers\\ErrorReportController',
|
||||
'/export' => 'PhpMyAdmin\\Controllers\\Export\\ExportController',
|
||||
'/export/tables' => 'PhpMyAdmin\\Controllers\\Export\\TablesController',
|
||||
'/export/template/create' => 'PhpMyAdmin\\Controllers\\Export\\Template\\CreateController',
|
||||
'/export/template/delete' => 'PhpMyAdmin\\Controllers\\Export\\Template\\DeleteController',
|
||||
'/export/template/load' => 'PhpMyAdmin\\Controllers\\Export\\Template\\LoadController',
|
||||
'/export/template/update' => 'PhpMyAdmin\\Controllers\\Export\\Template\\UpdateController',
|
||||
'/gis-data-editor' => 'PhpMyAdmin\\Controllers\\GisDataEditorController',
|
||||
'/git-revision' => 'PhpMyAdmin\\Controllers\\GitInfoController',
|
||||
'/import' => 'PhpMyAdmin\\Controllers\\Import\\ImportController',
|
||||
'/import/simulate-dml' => 'PhpMyAdmin\\Controllers\\Import\\SimulateDmlController',
|
||||
'/import-status' => 'PhpMyAdmin\\Controllers\\Import\\StatusController',
|
||||
'/lint' => 'PhpMyAdmin\\Controllers\\LintController',
|
||||
'/logout' => 'PhpMyAdmin\\Controllers\\LogoutController',
|
||||
'/navigation' => 'PhpMyAdmin\\Controllers\\NavigationController',
|
||||
'/normalization' => 'PhpMyAdmin\\Controllers\\NormalizationController',
|
||||
'/preferences/export' => 'PhpMyAdmin\\Controllers\\Preferences\\ExportController',
|
||||
'/preferences/features' => 'PhpMyAdmin\\Controllers\\Preferences\\FeaturesController',
|
||||
'/preferences/import' => 'PhpMyAdmin\\Controllers\\Preferences\\ImportController',
|
||||
'/preferences/main-panel' => 'PhpMyAdmin\\Controllers\\Preferences\\MainPanelController',
|
||||
'/preferences/manage' => 'PhpMyAdmin\\Controllers\\Preferences\\ManageController',
|
||||
'/preferences/navigation' => 'PhpMyAdmin\\Controllers\\Preferences\\NavigationController',
|
||||
'/preferences/sql' => 'PhpMyAdmin\\Controllers\\Preferences\\SqlController',
|
||||
'/preferences/two-factor' => 'PhpMyAdmin\\Controllers\\Preferences\\TwoFactorController',
|
||||
'/recent-table' => 'PhpMyAdmin\\Controllers\\RecentTablesListController',
|
||||
'/schema-export' => 'PhpMyAdmin\\Controllers\\SchemaExportController',
|
||||
'/server/binlog' => 'PhpMyAdmin\\Controllers\\Server\\BinlogController',
|
||||
'/server/databases' => 'PhpMyAdmin\\Controllers\\Server\\DatabasesController',
|
||||
'/server/databases/create' => 'PhpMyAdmin\\Controllers\\Server\\Databases\\CreateController',
|
||||
'/server/databases/destroy' => 'PhpMyAdmin\\Controllers\\Server\\Databases\\DestroyController',
|
||||
'/server/export' => 'PhpMyAdmin\\Controllers\\Server\\ExportController',
|
||||
'/server/import' => 'PhpMyAdmin\\Controllers\\Server\\ImportController',
|
||||
'/server/privileges' => 'PhpMyAdmin\\Controllers\\Server\\PrivilegesController',
|
||||
'/server/privileges/account-lock' => 'PhpMyAdmin\\Controllers\\Server\\Privileges\\AccountLockController',
|
||||
'/server/privileges/account-unlock' => 'PhpMyAdmin\\Controllers\\Server\\Privileges\\AccountUnlockController',
|
||||
'/server/replication' => 'PhpMyAdmin\\Controllers\\Server\\ReplicationController',
|
||||
'/server/sql' => 'PhpMyAdmin\\Controllers\\Server\\SqlController',
|
||||
'/server/status/monitor/chart' => 'PhpMyAdmin\\Controllers\\Server\\Status\\Monitor\\ChartingDataController',
|
||||
'/server/status/monitor/slow-log' => 'PhpMyAdmin\\Controllers\\Server\\Status\\Monitor\\SlowLogController',
|
||||
'/server/status/monitor/general-log' => 'PhpMyAdmin\\Controllers\\Server\\Status\\Monitor\\GeneralLogController',
|
||||
'/server/status/monitor/log-vars' => 'PhpMyAdmin\\Controllers\\Server\\Status\\Monitor\\LogVarsController',
|
||||
'/server/status/monitor/query' => 'PhpMyAdmin\\Controllers\\Server\\Status\\Monitor\\QueryAnalyzerController',
|
||||
'/server/status/processes' => 'PhpMyAdmin\\Controllers\\Server\\Status\\ProcessesController',
|
||||
'/server/status/processes/refresh' => 'PhpMyAdmin\\Controllers\\Server\\Status\\Processes\\RefreshController',
|
||||
'/server/status/variables' => 'PhpMyAdmin\\Controllers\\Server\\Status\\VariablesController',
|
||||
'/server/user-groups' => 'PhpMyAdmin\\Controllers\\Server\\UserGroupsController',
|
||||
'/sql' => 'PhpMyAdmin\\Controllers\\Sql\\SqlController',
|
||||
'/sql/get-relational-values' => 'PhpMyAdmin\\Controllers\\Sql\\RelationalValuesController',
|
||||
'/sql/get-enum-values' => 'PhpMyAdmin\\Controllers\\Sql\\EnumValuesController',
|
||||
'/sql/get-set-values' => 'PhpMyAdmin\\Controllers\\Sql\\SetValuesController',
|
||||
'/sql/set-column-preferences' => 'PhpMyAdmin\\Controllers\\Sql\\ColumnPreferencesController',
|
||||
'/table/add-field' => 'PhpMyAdmin\\Controllers\\Table\\AddFieldController',
|
||||
'/table/change' => 'PhpMyAdmin\\Controllers\\Table\\ChangeController',
|
||||
'/table/change/rows' => 'PhpMyAdmin\\Controllers\\Table\\ChangeRowsController',
|
||||
'/table/chart' => 'PhpMyAdmin\\Controllers\\Table\\ChartController',
|
||||
'/table/create' => 'PhpMyAdmin\\Controllers\\Table\\CreateController',
|
||||
'/table/delete/confirm' => 'PhpMyAdmin\\Controllers\\Table\\DeleteConfirmController',
|
||||
'/table/delete/rows' => 'PhpMyAdmin\\Controllers\\Table\\DeleteRowsController',
|
||||
'/table/export' => 'PhpMyAdmin\\Controllers\\Table\\ExportController',
|
||||
'/table/export/rows' => 'PhpMyAdmin\\Controllers\\Table\\ExportRowsController',
|
||||
'/table/find-replace' => 'PhpMyAdmin\\Controllers\\Table\\FindReplaceController',
|
||||
'/table/get-field' => 'PhpMyAdmin\\Controllers\\Table\\GetFieldController',
|
||||
'/table/gis-visualization' => 'PhpMyAdmin\\Controllers\\Table\\GisVisualizationController',
|
||||
'/table/import' => 'PhpMyAdmin\\Controllers\\Table\\ImportController',
|
||||
'/table/indexes' => 'PhpMyAdmin\\Controllers\\Table\\IndexesController',
|
||||
'/table/indexes/rename' => 'PhpMyAdmin\\Controllers\\Table\\IndexRenameController',
|
||||
'/table/maintenance/analyze' => 'PhpMyAdmin\\Controllers\\Table\\Maintenance\\AnalyzeController',
|
||||
'/table/maintenance/check' => 'PhpMyAdmin\\Controllers\\Table\\Maintenance\\CheckController',
|
||||
'/table/maintenance/checksum' => 'PhpMyAdmin\\Controllers\\Table\\Maintenance\\ChecksumController',
|
||||
'/table/maintenance/optimize' => 'PhpMyAdmin\\Controllers\\Table\\Maintenance\\OptimizeController',
|
||||
'/table/maintenance/repair' => 'PhpMyAdmin\\Controllers\\Table\\Maintenance\\RepairController',
|
||||
'/table/partition/analyze' => 'PhpMyAdmin\\Controllers\\Table\\Partition\\AnalyzeController',
|
||||
'/table/partition/check' => 'PhpMyAdmin\\Controllers\\Table\\Partition\\CheckController',
|
||||
'/table/partition/drop' => 'PhpMyAdmin\\Controllers\\Table\\Partition\\DropController',
|
||||
'/table/partition/optimize' => 'PhpMyAdmin\\Controllers\\Table\\Partition\\OptimizeController',
|
||||
'/table/partition/rebuild' => 'PhpMyAdmin\\Controllers\\Table\\Partition\\RebuildController',
|
||||
'/table/partition/repair' => 'PhpMyAdmin\\Controllers\\Table\\Partition\\RepairController',
|
||||
'/table/partition/truncate' => 'PhpMyAdmin\\Controllers\\Table\\Partition\\TruncateController',
|
||||
'/table/operations' => 'PhpMyAdmin\\Controllers\\Table\\OperationsController',
|
||||
'/table/recent-favorite' => 'PhpMyAdmin\\Controllers\\Table\\RecentFavoriteController',
|
||||
'/table/relation' => 'PhpMyAdmin\\Controllers\\Table\\RelationController',
|
||||
'/table/replace' => 'PhpMyAdmin\\Controllers\\Table\\ReplaceController',
|
||||
'/table/search' => 'PhpMyAdmin\\Controllers\\Table\\SearchController',
|
||||
'/table/sql' => 'PhpMyAdmin\\Controllers\\Table\\SqlController',
|
||||
'/table/structure' => 'PhpMyAdmin\\Controllers\\Table\\StructureController',
|
||||
'/table/structure/add-key' => 'PhpMyAdmin\\Controllers\\Table\\Structure\\AddKeyController',
|
||||
'/table/structure/browse' => 'PhpMyAdmin\\Controllers\\Table\\Structure\\BrowseController',
|
||||
'/table/structure/central-columns-add' => 'PhpMyAdmin\\Controllers\\Table\\Structure\\CentralColumnsAddController',
|
||||
'/table/structure/central-columns-remove' => 'PhpMyAdmin\\Controllers\\Table\\Structure\\CentralColumnsRemoveController',
|
||||
'/table/structure/change' => 'PhpMyAdmin\\Controllers\\Table\\Structure\\ChangeController',
|
||||
'/table/structure/drop' => 'PhpMyAdmin\\Controllers\\Table\\DropColumnController',
|
||||
'/table/structure/drop-confirm' => 'PhpMyAdmin\\Controllers\\Table\\DropColumnConfirmationController',
|
||||
'/table/structure/fulltext' => 'PhpMyAdmin\\Controllers\\Table\\Structure\\FulltextController',
|
||||
'/table/structure/index' => 'PhpMyAdmin\\Controllers\\Table\\Structure\\AddIndexController',
|
||||
'/table/structure/move-columns' => 'PhpMyAdmin\\Controllers\\Table\\Structure\\MoveColumnsController',
|
||||
'/table/structure/partitioning' => 'PhpMyAdmin\\Controllers\\Table\\Structure\\PartitioningController',
|
||||
'/table/structure/primary' => 'PhpMyAdmin\\Controllers\\Table\\Structure\\PrimaryController',
|
||||
'/table/structure/reserved-word-check' => 'PhpMyAdmin\\Controllers\\Table\\Structure\\ReservedWordCheckController',
|
||||
'/table/structure/save' => 'PhpMyAdmin\\Controllers\\Table\\Structure\\SaveController',
|
||||
'/table/structure/spatial' => 'PhpMyAdmin\\Controllers\\Table\\Structure\\SpatialController',
|
||||
'/table/structure/unique' => 'PhpMyAdmin\\Controllers\\Table\\Structure\\UniqueController',
|
||||
'/table/tracking' => 'PhpMyAdmin\\Controllers\\Table\\TrackingController',
|
||||
'/table/triggers' => 'PhpMyAdmin\\Controllers\\Table\\TriggersController',
|
||||
'/table/zoom-search' => 'PhpMyAdmin\\Controllers\\Table\\ZoomSearchController',
|
||||
'/tables' => 'PhpMyAdmin\\Controllers\\TableController',
|
||||
'/themes/set' => 'PhpMyAdmin\\Controllers\\ThemeSetController',
|
||||
'/transformation/overview' => 'PhpMyAdmin\\Controllers\\Transformation\\OverviewController',
|
||||
'/transformation/wrapper' => 'PhpMyAdmin\\Controllers\\Transformation\\WrapperController',
|
||||
'/user-password' => 'PhpMyAdmin\\Controllers\\UserPasswordController',
|
||||
'/version-check' => 'PhpMyAdmin\\Controllers\\VersionCheckController',
|
||||
'/view/create' => 'PhpMyAdmin\\Controllers\\View\\CreateController',
|
||||
'/view/operations' => 'PhpMyAdmin\\Controllers\\View\\OperationsController',
|
||||
),
|
||||
),
|
||||
1 =>
|
||||
array (
|
||||
'GET' =>
|
||||
array (
|
||||
0 =>
|
||||
array (
|
||||
'regex' => '~^(?|/server/engines/([^/]+)|/server/engines/([^/]+)/([^/]+)|/server/variables/get/([^/]+)()())$~',
|
||||
'routeMap' =>
|
||||
array (
|
||||
2 =>
|
||||
array (
|
||||
0 => 'PhpMyAdmin\\Controllers\\Server\\ShowEngineController',
|
||||
1 =>
|
||||
array (
|
||||
'engine' => 'engine',
|
||||
),
|
||||
),
|
||||
3 =>
|
||||
array (
|
||||
0 => 'PhpMyAdmin\\Controllers\\Server\\ShowEngineController',
|
||||
1 =>
|
||||
array (
|
||||
'engine' => 'engine',
|
||||
'page' => 'page',
|
||||
),
|
||||
),
|
||||
4 =>
|
||||
array (
|
||||
0 => 'PhpMyAdmin\\Controllers\\Server\\Variables\\GetVariableController',
|
||||
1 =>
|
||||
array (
|
||||
'name' => 'name',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
'POST' =>
|
||||
array (
|
||||
0 =>
|
||||
array (
|
||||
'regex' => '~^(?|/server/status/processes/kill/(\\d+)|/server/variables/set/([^/]+)())$~',
|
||||
'routeMap' =>
|
||||
array (
|
||||
2 =>
|
||||
array (
|
||||
0 => 'PhpMyAdmin\\Controllers\\Server\\Status\\Processes\\KillController',
|
||||
1 =>
|
||||
array (
|
||||
'id' => 'id',
|
||||
),
|
||||
),
|
||||
3 =>
|
||||
array (
|
||||
0 => 'PhpMyAdmin\\Controllers\\Server\\Variables\\SetVariableController',
|
||||
1 =>
|
||||
array (
|
||||
'name' => 'name',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
433
admin/phpMyAdmin/libraries/classes/Advisor.php
Normal file
433
admin/phpMyAdmin/libraries/classes/Advisor.php
Normal file
|
@ -0,0 +1,433 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin;
|
||||
|
||||
use PhpMyAdmin\Server\SysInfo\SysInfo;
|
||||
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
|
||||
use Throwable;
|
||||
|
||||
use function __;
|
||||
use function array_merge;
|
||||
use function htmlspecialchars;
|
||||
use function implode;
|
||||
use function preg_match;
|
||||
use function preg_replace_callback;
|
||||
use function round;
|
||||
use function sprintf;
|
||||
use function str_contains;
|
||||
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 (): void {
|
||||
},
|
||||
/**
|
||||
* @param array $arguments
|
||||
* @param float $num
|
||||
*/
|
||||
static function ($arguments, $num) {
|
||||
return round($num);
|
||||
}
|
||||
);
|
||||
$this->expression->register(
|
||||
'substr',
|
||||
static function (): void {
|
||||
},
|
||||
/**
|
||||
* @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 (): void {
|
||||
},
|
||||
/**
|
||||
* @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 (): void {
|
||||
},
|
||||
/**
|
||||
* @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 (): void {
|
||||
},
|
||||
/**
|
||||
* @param array $arguments
|
||||
* @param string $seconds
|
||||
*/
|
||||
static function ($arguments, $seconds) {
|
||||
return Util::timespanFormat((int) $seconds);
|
||||
}
|
||||
);
|
||||
$this->expression->register(
|
||||
'ADVISOR_formatByteDown',
|
||||
static function (): void {
|
||||
},
|
||||
/**
|
||||
* @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 (): void {
|
||||
},
|
||||
/**
|
||||
* @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(),
|
||||
'IS_MARIADB' => $this->dbi->isMariaDB(),
|
||||
];
|
||||
}
|
||||
|
||||
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 = str_contains($this->variables['version'], 'MariaDB');
|
||||
$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 = '<' . 10 ** (-$precision);
|
||||
}
|
||||
|
||||
return $num . ' ' . $per;
|
||||
}
|
||||
}
|
329
admin/phpMyAdmin/libraries/classes/Bookmark.php
Normal file
329
admin/phpMyAdmin/libraries/classes/Bookmark.php
Normal file
|
@ -0,0 +1,329 @@
|
|||
<?php
|
||||
/**
|
||||
* Handles bookmarking SQL queries
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin;
|
||||
|
||||
use PhpMyAdmin\ConfigStorage\Features\BookmarkFeature;
|
||||
use PhpMyAdmin\ConfigStorage\Relation;
|
||||
|
||||
use function count;
|
||||
use function preg_match_all;
|
||||
use function preg_replace;
|
||||
use function str_replace;
|
||||
use function strlen;
|
||||
|
||||
use const PREG_SET_ORDER;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/** @var Relation */
|
||||
private $relation;
|
||||
|
||||
public function __construct(DatabaseInterface $dbi, Relation $relation)
|
||||
{
|
||||
$this->dbi = $dbi;
|
||||
$this->relation = $relation;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public function save(): bool
|
||||
{
|
||||
$bookmarkFeature = $this->relation->getRelationParameters()->bookmarkFeature;
|
||||
if ($bookmarkFeature === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$query = 'INSERT INTO ' . Util::backquote($bookmarkFeature->database)
|
||||
. '.' . Util::backquote($bookmarkFeature->bookmark)
|
||||
. ' (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 (bool) $this->dbi->query($query, DatabaseInterface::CONNECT_CONTROL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a bookmark
|
||||
*/
|
||||
public function delete(): bool
|
||||
{
|
||||
$bookmarkFeature = $this->relation->getRelationParameters()->bookmarkFeature;
|
||||
if ($bookmarkFeature === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$query = 'DELETE FROM ' . Util::backquote($bookmarkFeature->database)
|
||||
. '.' . Util::backquote($bookmarkFeature->bookmark)
|
||||
. ' WHERE id = ' . $this->id;
|
||||
|
||||
return (bool) $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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Bookmark object from the parameters
|
||||
*
|
||||
* @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, 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, new Relation($dbi));
|
||||
$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 array $row Resource used to build the bookmark
|
||||
*/
|
||||
protected static function createFromRow(DatabaseInterface $dbi, $row): Bookmark
|
||||
{
|
||||
$bookmark = new Bookmark($dbi, new Relation($dbi));
|
||||
$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
|
||||
*/
|
||||
public static function getList(
|
||||
BookmarkFeature $bookmarkFeature,
|
||||
DatabaseInterface $dbi,
|
||||
string $user,
|
||||
$db = false
|
||||
): array {
|
||||
$query = 'SELECT * FROM ' . Util::backquote($bookmarkFeature->database)
|
||||
. '.' . Util::backquote($bookmarkFeature->bookmark)
|
||||
. " WHERE ( `user` = ''"
|
||||
. " OR `user` = '" . $dbi->escapeString($user) . "' )";
|
||||
if ($db !== false) {
|
||||
$query .= " AND dbase = '" . $dbi->escapeString($db) . "'";
|
||||
}
|
||||
|
||||
$query .= ' ORDER BY label ASC';
|
||||
|
||||
$result = $dbi->fetchResult(
|
||||
$query,
|
||||
null,
|
||||
null,
|
||||
DatabaseInterface::CONNECT_CONTROL
|
||||
);
|
||||
|
||||
if (! empty($result)) {
|
||||
$bookmarks = [];
|
||||
foreach ($result as $row) {
|
||||
$bookmarks[] = self::createFromRow($dbi, $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
|
||||
*/
|
||||
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 {
|
||||
$relation = new Relation($dbi);
|
||||
$bookmarkFeature = $relation->getRelationParameters()->bookmarkFeature;
|
||||
if ($bookmarkFeature === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$query = 'SELECT * FROM ' . Util::backquote($bookmarkFeature->database)
|
||||
. '.' . Util::backquote($bookmarkFeature->bookmark)
|
||||
. " WHERE dbase = '" . $dbi->escapeString($db) . "'";
|
||||
if (! $action_bookmark_all) {
|
||||
$query .= " AND (user = '"
|
||||
. $dbi->escapeString($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, DatabaseInterface::FETCH_ASSOC, DatabaseInterface::CONNECT_CONTROL);
|
||||
if (! empty($result)) {
|
||||
return self::createFromRow($dbi, $result);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
343
admin/phpMyAdmin/libraries/classes/BrowseForeigners.php
Normal file
343
admin/phpMyAdmin/libraries/classes/BrowseForeigners.php
Normal file
|
@ -0,0 +1,343 @@
|
|||
<?php
|
||||
/**
|
||||
* Contains functions used by browse foreigners
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin;
|
||||
|
||||
use function __;
|
||||
use function array_keys;
|
||||
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 Template */
|
||||
public $template;
|
||||
|
||||
/**
|
||||
* @param Template $template Template object
|
||||
*/
|
||||
public function __construct(Template $template)
|
||||
{
|
||||
global $cfg;
|
||||
|
||||
$this->template = $template;
|
||||
|
||||
$this->limitChars = (int) $cfg['LimitChars'];
|
||||
$this->maxRows = (int) $cfg['MaxRows'];
|
||||
$this->repeatCells = (int) $cfg['RepeatCells'];
|
||||
$this->showAll = (bool) $cfg['ShowAll'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to get html for one relational key
|
||||
*
|
||||
* @param int $horizontalCount 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 $currentValue current value on the edit form
|
||||
*
|
||||
* @return array the generated html
|
||||
*/
|
||||
private function getHtmlForOneKey(
|
||||
int $horizontalCount,
|
||||
string $header,
|
||||
array $keys,
|
||||
int $indexByKeyname,
|
||||
array $descriptions,
|
||||
int $indexByDescription,
|
||||
string $currentValue
|
||||
): array {
|
||||
global $theme;
|
||||
|
||||
$horizontalCount++;
|
||||
$output = '';
|
||||
|
||||
// whether the key name corresponds to the selected value in the form
|
||||
$rightKeynameIsSelected = false;
|
||||
$leftKeynameIsSelected = false;
|
||||
|
||||
if ($this->repeatCells > 0 && $horizontalCount > $this->repeatCells) {
|
||||
$output .= $header;
|
||||
$horizontalCount = 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($currentValue)) {
|
||||
$rightKeynameIsSelected = $rightKeyname == $currentValue;
|
||||
$leftKeynameIsSelected = $leftKeyname == $currentValue;
|
||||
}
|
||||
|
||||
$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="'
|
||||
. ($theme instanceof Theme ? $theme->getImgPath('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,
|
||||
$horizontalCount,
|
||||
$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 $currentValue current columns's value
|
||||
*/
|
||||
public function getHtmlForRelationalFieldSelection(
|
||||
string $db,
|
||||
string $table,
|
||||
string $field,
|
||||
array $foreignData,
|
||||
?string $fieldKey,
|
||||
string $currentValue
|
||||
): 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 class="row g-3 align-items-center mb-3">'
|
||||
. 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']) . '">';
|
||||
}
|
||||
|
||||
$filterValue = (isset($_POST['foreign_filter'])
|
||||
? htmlspecialchars($_POST['foreign_filter'])
|
||||
: '');
|
||||
$output .= '<div class="col-auto">'
|
||||
. '<label class="form-label" for="input_foreign_filter">' . __('Search:') . '</label></div>'
|
||||
. '<div class="col-auto"><input class="form-control" type="text" name="foreign_filter" '
|
||||
. 'id="input_foreign_filter" '
|
||||
. 'value="' . $filterValue . '" data-old="' . $filterValue . '">'
|
||||
. '</div><div class="col-auto">'
|
||||
. '<input class="btn btn-primary" type="submit" name="submit_foreign_filter" value="'
|
||||
. __('Go') . '">'
|
||||
. '</div>'
|
||||
. '<div class="col-auto">' . $gotoPage . '</div>'
|
||||
. '<div class="col-auto">' . $foreignShowAll . '</div>'
|
||||
. '</fieldset>'
|
||||
. '</form>';
|
||||
|
||||
$output .= '<table class="table table-striped table-hover" 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);
|
||||
|
||||
$horizontalCount = 0;
|
||||
$indexByDescription = 0;
|
||||
|
||||
foreach (array_keys($keys) as $indexByKeyname) {
|
||||
[
|
||||
$html,
|
||||
$horizontalCount,
|
||||
$indexByDescription,
|
||||
] = $this->getHtmlForOneKey(
|
||||
$horizontalCount,
|
||||
$header,
|
||||
$keys,
|
||||
$indexByKeyname,
|
||||
$descriptions,
|
||||
$indexByDescription,
|
||||
$currentValue
|
||||
);
|
||||
$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) {
|
||||
$descriptionTitle = '';
|
||||
} else {
|
||||
$descriptionTitle = $description;
|
||||
$description = 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 . ' ';
|
||||
}
|
||||
}
|
74
admin/phpMyAdmin/libraries/classes/Cache.php
Normal file
74
admin/phpMyAdmin/libraries/classes/Cache.php
Normal file
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin;
|
||||
|
||||
use function array_key_exists;
|
||||
|
||||
/**
|
||||
* Cache values
|
||||
*/
|
||||
class Cache
|
||||
{
|
||||
/** @var array<string,mixed> */
|
||||
private static $cacheData = [];
|
||||
|
||||
/**
|
||||
* Store a value
|
||||
*
|
||||
* @param string $cacheKey The key to use
|
||||
* @param mixed $value The value to cache
|
||||
*/
|
||||
public static function set(string $cacheKey, $value): bool
|
||||
{
|
||||
self::$cacheData[$cacheKey] = $value;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the cache have a value stored for the key
|
||||
*
|
||||
* @param string $cacheKey The key to use
|
||||
*/
|
||||
public static function has(string $cacheKey): bool
|
||||
{
|
||||
return array_key_exists($cacheKey, self::$cacheData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get back a cached value
|
||||
*
|
||||
* @param string $cacheKey The key to use
|
||||
* @param mixed $defaultValue The default value in case it does not exist
|
||||
*
|
||||
* @return mixed The cached value
|
||||
*/
|
||||
public static function get(string $cacheKey, $defaultValue = null)
|
||||
{
|
||||
return self::$cacheData[$cacheKey] ?? $defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a cached value
|
||||
*
|
||||
* @param string $cacheKey The key to use to remove the value
|
||||
*/
|
||||
public static function remove(string $cacheKey): bool
|
||||
{
|
||||
unset(self::$cacheData[$cacheKey]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Purge all cached values
|
||||
*/
|
||||
public static function purge(): bool
|
||||
{
|
||||
self::$cacheData = [];
|
||||
|
||||
return self::$cacheData === [];
|
||||
}
|
||||
}
|
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 function __;
|
||||
use function array_keys;
|
||||
use function count;
|
||||
use function explode;
|
||||
use function is_string;
|
||||
use function ksort;
|
||||
|
||||
use const SORT_STRING;
|
||||
|
||||
/**
|
||||
* Class used to manage MySQL charsets
|
||||
*/
|
||||
class Charsets
|
||||
{
|
||||
/**
|
||||
* MySQL charsets map
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
$sql = 'SELECT `CHARACTER_SET_NAME` AS `Charset`,'
|
||||
. ' `DEFAULT_COLLATE_NAME` AS `Default collation`,'
|
||||
. ' `DESCRIPTION` AS `Description`,'
|
||||
. ' `MAXLEN` AS `Maxlen`'
|
||||
. ' FROM `information_schema`.`CHARACTER_SETS`';
|
||||
|
||||
if ($disableIs) {
|
||||
$sql = 'SHOW CHARACTER SET';
|
||||
}
|
||||
|
||||
$res = $dbi->query($sql);
|
||||
|
||||
self::$charsets = [];
|
||||
foreach ($res as $row) {
|
||||
self::$charsets[$row['Charset']] = Charset::fromServer($row);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
$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`';
|
||||
|
||||
if ($disableIs) {
|
||||
$sql = 'SHOW COLLATION';
|
||||
}
|
||||
|
||||
$res = $dbi->query($sql);
|
||||
|
||||
self::$collations = [];
|
||||
foreach ($res as $row) {
|
||||
self::$collations[$row['Charset']][$row['Collation']] = Collation::fromServer($row);
|
||||
}
|
||||
|
||||
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<string, Charset>
|
||||
*/
|
||||
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<string, array<string, Collation>>
|
||||
*/
|
||||
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
|
||||
{
|
||||
$charset = explode('_', $name ?? '')[0];
|
||||
$collations = self::getCollations($dbi, $disableIs);
|
||||
|
||||
return $collations[$charset][$name] ?? null;
|
||||
}
|
||||
}
|
96
admin/phpMyAdmin/libraries/classes/Charsets/Charset.php
Normal file
96
admin/phpMyAdmin/libraries/classes/Charsets/Charset.php
Normal file
|
@ -0,0 +1,96 @@
|
|||
<?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 string[] $state State obtained from the database server
|
||||
* @psalm-param array{Charset?:string, Description?:string, 'Default collation'?:string, Maxlen?:string} $state
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
}
|
611
admin/phpMyAdmin/libraries/classes/Charsets/Collation.php
Normal file
611
admin/phpMyAdmin/libraries/classes/Charsets/Collation.php
Normal file
|
@ -0,0 +1,611 @@
|
|||
<?php
|
||||
/**
|
||||
* Value object class for a collation
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Charsets;
|
||||
|
||||
use function __;
|
||||
use function _pgettext;
|
||||
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 string[] $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 */
|
||||
[$name, $unicode, $unknown, $variant] = $this->getNameForLevel0($unicode, $unknown, $part, $variant);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($level === 1) {
|
||||
/* Next will be variant unless changed later */
|
||||
$level = 4;
|
||||
/* Locale name or code */
|
||||
$found = true;
|
||||
[$name, $level, $found] = $this->getNameForLevel1($unicode, $unknown, $part, $name, $level, $found);
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $suffixes
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, bool|string|null>
|
||||
* @psalm-return array{string, bool, bool, string|null}
|
||||
*/
|
||||
private function getNameForLevel0(
|
||||
bool $unicode,
|
||||
bool $unknown,
|
||||
string $part,
|
||||
?string $variant
|
||||
): array {
|
||||
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 'utf8mb3':
|
||||
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;
|
||||
}
|
||||
|
||||
return [$name, $unicode, $unknown, $variant];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, bool|int|string>
|
||||
* @psalm-return array{string, int, bool}
|
||||
*/
|
||||
private function getNameForLevel1(
|
||||
bool $unicode,
|
||||
bool $unknown,
|
||||
string $part,
|
||||
string $name,
|
||||
int $level,
|
||||
bool $found
|
||||
): array {
|
||||
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;
|
||||
}
|
||||
|
||||
return [$name, $level, $found];
|
||||
}
|
||||
}
|
330
admin/phpMyAdmin/libraries/classes/CheckUserPrivileges.php
Normal file
330
admin/phpMyAdmin/libraries/classes/CheckUserPrivileges.php
Normal file
|
@ -0,0 +1,330 @@
|
|||
<?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 str_contains;
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
$dbNameOffset = mb_strpos($row, ' ON ') + 4;
|
||||
|
||||
$tableNameEndOffset = mb_strpos($row, ' TO ');
|
||||
$tableNameStartOffset = false;
|
||||
$tableNameStartOffset2 = mb_strpos($row, '`.', $dbNameOffset);
|
||||
|
||||
if ($tableNameStartOffset2 && $tableNameStartOffset2 < $tableNameEndOffset) {
|
||||
$tableNameStartOffset = $tableNameStartOffset2 + 1;
|
||||
}
|
||||
|
||||
if ($tableNameStartOffset === false) {
|
||||
$tableNameStartOffset = mb_strpos($row, '.', $dbNameOffset);
|
||||
}
|
||||
|
||||
$showGrantsDbName = mb_substr($row, $dbNameOffset, $tableNameStartOffset - $dbNameOffset);
|
||||
|
||||
$showGrantsDbName = Util::unQuote($showGrantsDbName, '`');
|
||||
|
||||
$showGrantsString = mb_substr(
|
||||
$row,
|
||||
6,
|
||||
mb_strpos($row, ' ON ') - 6
|
||||
);
|
||||
|
||||
$showGrantsTableName = mb_substr(
|
||||
$row,
|
||||
$tableNameStartOffset + 1,
|
||||
$tableNameEndOffset - $tableNameStartOffset - 1
|
||||
);
|
||||
$showGrantsTableName = Util::unQuote($showGrantsTableName, '`');
|
||||
|
||||
return [
|
||||
$showGrantsString,
|
||||
$showGrantsDbName,
|
||||
$showGrantsTableName,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user has required privileges for
|
||||
* performing 'Adjust privileges' operations
|
||||
*
|
||||
* @param string $showGrantsString string containing grants for user
|
||||
* @param string $showGrantsDbName name of db extracted from grant string
|
||||
* @param string $showGrantsTableName name of table extracted from grant string
|
||||
*/
|
||||
public function checkRequiredPrivilegesForAdjust(
|
||||
string $showGrantsString,
|
||||
string $showGrantsDbName,
|
||||
string $showGrantsTableName
|
||||
): void {
|
||||
// '... ALL PRIVILEGES ON *.* ...' OR '... ALL PRIVILEGES ON `mysql`.* ..'
|
||||
// OR
|
||||
// SELECT, INSERT, UPDATE, DELETE .... ON *.* OR `mysql`.*
|
||||
if (
|
||||
$showGrantsString !== 'ALL'
|
||||
&& $showGrantsString !== 'ALL PRIVILEGES'
|
||||
&& (mb_strpos($showGrantsString, 'SELECT, INSERT, UPDATE, DELETE') === false)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($showGrantsDbName === '*' && $showGrantsTableName === '*') {
|
||||
$GLOBALS['col_priv'] = true;
|
||||
$GLOBALS['db_priv'] = true;
|
||||
$GLOBALS['proc_priv'] = true;
|
||||
$GLOBALS['table_priv'] = true;
|
||||
|
||||
if ($showGrantsString === 'ALL PRIVILEGES' || $showGrantsString === 'ALL') {
|
||||
$GLOBALS['is_reload_priv'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// check for specific tables in `mysql` db
|
||||
// Ex. '... ALL PRIVILEGES on `mysql`.`columns_priv` .. '
|
||||
if ($showGrantsDbName !== 'mysql') {
|
||||
return;
|
||||
}
|
||||
|
||||
switch ($showGrantsTableName) {
|
||||
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;
|
||||
|
||||
$showGrantsResult = $this->dbi->tryQuery('SHOW GRANTS');
|
||||
|
||||
if (! $showGrantsResult) {
|
||||
return;
|
||||
}
|
||||
|
||||
$re0 = '(^|(\\\\\\\\)+|[^\\\\])'; // non-escaped wildcards
|
||||
$re1 = '(^|[^\\\\])(\\\)+'; // escaped wildcards
|
||||
|
||||
while ($row = $showGrantsResult->fetchRow()) {
|
||||
[
|
||||
$showGrantsString,
|
||||
$showGrantsDbName,
|
||||
$showGrantsTableName,
|
||||
] = $this->getItemsFromShowGrantsRow($row[0]);
|
||||
|
||||
if ($showGrantsDbName === '*') {
|
||||
if ($showGrantsString !== 'USAGE') {
|
||||
$GLOBALS['dbs_to_test'] = false;
|
||||
}
|
||||
} elseif ($GLOBALS['dbs_to_test'] !== false) {
|
||||
$GLOBALS['dbs_to_test'][] = $showGrantsDbName;
|
||||
}
|
||||
|
||||
if (str_contains($showGrantsString, 'RELOAD')) {
|
||||
$GLOBALS['is_reload_priv'] = true;
|
||||
}
|
||||
|
||||
// check for the required privileges for adjust
|
||||
$this->checkRequiredPrivilegesForAdjust($showGrantsString, $showGrantsDbName, $showGrantsTableName);
|
||||
|
||||
/**
|
||||
* @todo if we find CREATE VIEW but not CREATE, do not offer
|
||||
* the create database dialog box
|
||||
*/
|
||||
if (
|
||||
$showGrantsString !== 'ALL'
|
||||
&& $showGrantsString !== 'ALL PRIVILEGES'
|
||||
&& $showGrantsString !== 'CREATE'
|
||||
&& ! str_contains($showGrantsString, 'CREATE,')
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($showGrantsDbName === '*') {
|
||||
// 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'][] = $showGrantsDbName;
|
||||
|
||||
$dbNameToTest = Util::backquote($showGrantsDbName);
|
||||
|
||||
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 . '%|_/', $showGrantsDbName)
|
||||
|| preg_match('/\\\\%|\\\\_/', $showGrantsDbName))
|
||||
&& ($this->dbi->tryQuery(
|
||||
'USE ' . preg_replace(
|
||||
'/' . $re1 . '(%|_)/',
|
||||
'\\1\\3',
|
||||
$dbNameToTest
|
||||
)
|
||||
)
|
||||
|| mb_substr($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', $showGrantsDbName);
|
||||
$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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->analyseShowGrant();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,193 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Command;
|
||||
|
||||
use PhpMyAdmin\Config;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Routing;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Tests\Stubs\DbiDummy;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
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 Twig\Cache\CacheInterface;
|
||||
|
||||
use function file_put_contents;
|
||||
use function is_file;
|
||||
use function json_encode;
|
||||
use function sprintf;
|
||||
use function str_contains;
|
||||
use function str_replace;
|
||||
|
||||
use const CACHE_DIR;
|
||||
|
||||
final class CacheWarmupCommand extends Command
|
||||
{
|
||||
/** @var string|null */
|
||||
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->addOption('twig-po', null, null, 'Warm up twig templates and write file mappings.');
|
||||
$this->addOption(
|
||||
'env',
|
||||
null,
|
||||
InputArgument::OPTIONAL,
|
||||
'Defines the environment (production or development) for twig warmup',
|
||||
'production'
|
||||
);
|
||||
$this->setHelp('The <info>%command.name%</info> command warms up the cache of the Twig templates.');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
/** @var string $env */
|
||||
$env = $input->getOption('env');
|
||||
|
||||
if ($input->getOption('twig') === true && $input->getOption('routing') === true) {
|
||||
$output->writeln('Please specify --twig or --routing');
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
if ($input->getOption('twig') === true) {
|
||||
return $this->warmUpTwigCache($output, $env, false);
|
||||
}
|
||||
|
||||
if ($input->getOption('twig-po') === true) {
|
||||
return $this->warmUpTwigCache($output, $env, true);
|
||||
}
|
||||
|
||||
if ($input->getOption('routing') === true) {
|
||||
return $this->warmUpRoutingCache($output);
|
||||
}
|
||||
|
||||
$output->writeln('Warming up all caches.', OutputInterface::VERBOSITY_VERBOSE);
|
||||
$twigCode = $this->warmUpTwigCache($output, $env, false);
|
||||
if ($twigCode !== 0) {
|
||||
$output->writeln('Twig cache generation had an error.');
|
||||
|
||||
return $twigCode;
|
||||
}
|
||||
|
||||
$routingCode = $this->warmUpRoutingCache($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 Command::SUCCESS;
|
||||
}
|
||||
|
||||
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 Command::SUCCESS;
|
||||
}
|
||||
|
||||
$output->writeln(
|
||||
sprintf(
|
||||
'Warm up did not work, the folder "%s" is probably not writable.',
|
||||
CACHE_DIR
|
||||
),
|
||||
OutputInterface::VERBOSITY_NORMAL
|
||||
);
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
private function warmUpTwigCache(
|
||||
OutputInterface $output,
|
||||
string $environment,
|
||||
bool $writeReplacements
|
||||
): int {
|
||||
global $cfg, $config, $dbi;
|
||||
|
||||
$output->writeln('Warming up the twig cache', OutputInterface::VERBOSITY_VERBOSE);
|
||||
$config = new Config(CONFIG_FILE);
|
||||
$cfg['environment'] = $environment;
|
||||
$config->set('environment', $cfg['environment']);
|
||||
$dbi = new DatabaseInterface(new DbiDummy());
|
||||
$tmpDir = ROOT_PATH . 'twig-templates';
|
||||
$twig = Template::getTwigEnvironment($tmpDir);
|
||||
|
||||
$output->writeln('Searching for files...', OutputInterface::VERBOSITY_VERY_VERBOSE);
|
||||
|
||||
$templates = new RecursiveIteratorIterator(
|
||||
new RecursiveDirectoryIterator(Template::TEMPLATES_FOLDER),
|
||||
RecursiveIteratorIterator::LEAVES_ONLY
|
||||
);
|
||||
|
||||
/** @var CacheInterface $twigCache */
|
||||
$twigCache = $twig->getCache(false);
|
||||
$replacements = [];
|
||||
$output->writeln(
|
||||
'Twig debug is: ' . ($twig->isDebug() ? 'enabled' : 'disabled'),
|
||||
OutputInterface::VERBOSITY_DEBUG
|
||||
);
|
||||
|
||||
$output->writeln('Warming templates', OutputInterface::VERBOSITY_VERY_VERBOSE);
|
||||
foreach ($templates as $file) {
|
||||
// Skip test files
|
||||
if (str_contains($file->getPathname(), '/test/')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// force compilation
|
||||
if (! $file->isFile() || $file->getExtension() !== 'twig') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$name = str_replace(Template::TEMPLATES_FOLDER . '/', '', $file->getPathname());
|
||||
$output->writeln('Loading: ' . $name, OutputInterface::VERBOSITY_DEBUG);
|
||||
/** @psalm-suppress InternalMethod */
|
||||
$template = $twig->loadTemplate($twig->getTemplateClass($name), $name);
|
||||
|
||||
if (! $writeReplacements) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Generate line map
|
||||
/** @psalm-suppress InternalMethod */
|
||||
$cacheFilename = $twigCache->generateKey($name, $twig->getTemplateClass($name));
|
||||
$template_file = 'templates/' . $name;
|
||||
$cache_file = str_replace($tmpDir, 'twig-templates', $cacheFilename);
|
||||
/** @psalm-suppress InternalMethod */
|
||||
$replacements[$cache_file] = [$template_file, $template->getDebugInfo()];
|
||||
}
|
||||
|
||||
if (! $writeReplacements) {
|
||||
$output->writeln('Warm up done.', OutputInterface::VERBOSITY_VERBOSE);
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
$output->writeln('Writing replacements...', OutputInterface::VERBOSITY_VERY_VERBOSE);
|
||||
|
||||
// Store replacements in JSON
|
||||
if (file_put_contents($tmpDir . '/replace.json', (string) json_encode($replacements)) === false) {
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$output->writeln('Replacements written done.', OutputInterface::VERBOSITY_VERBOSE);
|
||||
$output->writeln('Warm up done.', OutputInterface::VERBOSITY_VERBOSE);
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Command;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
use function file_get_contents;
|
||||
use function file_put_contents;
|
||||
use function intval;
|
||||
use function is_array;
|
||||
use function json_decode;
|
||||
use function preg_replace_callback;
|
||||
|
||||
use const ROOT_PATH;
|
||||
|
||||
final class FixPoTwigCommand extends Command
|
||||
{
|
||||
/** @var string|null */
|
||||
protected static $defaultName = 'fix-po-twig';
|
||||
|
||||
private const POT_FILE = ROOT_PATH . 'po/phpmyadmin.pot';
|
||||
private const REPLACE_FILE = ROOT_PATH . 'twig-templates/replace.json';
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->setDescription('Fixes POT file for Twig templates');
|
||||
$this->setHelp(
|
||||
'The <info>%command.name%</info> command fixes the Twig file name and line number in the'
|
||||
. ' POT file to match the Twig template and not the compiled Twig file.'
|
||||
);
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$replaceFile = file_get_contents(self::REPLACE_FILE);
|
||||
if ($replaceFile === false) {
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$replacements = json_decode($replaceFile, true);
|
||||
if (! is_array($replacements)) {
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
/* Read pot file */
|
||||
$pot = file_get_contents(self::POT_FILE);
|
||||
if ($pot === false) {
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
/* Do the replacements */
|
||||
$pot = preg_replace_callback(
|
||||
'@(twig-templates[0-9a-f/]*.php):([0-9]*)@',
|
||||
static function (array $matches) use ($replacements): string {
|
||||
$filename = $matches[1];
|
||||
$line = intval($matches[2]);
|
||||
$replace = $replacements[$filename];
|
||||
foreach ($replace[1] as $cacheLine => $result) {
|
||||
if ($line >= $cacheLine) {
|
||||
return $replace[0] . ':' . $result;
|
||||
}
|
||||
}
|
||||
|
||||
return $replace[0] . ':0';
|
||||
},
|
||||
$pot
|
||||
);
|
||||
if ($pot === null) {
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
if (file_put_contents(self::POT_FILE, $pot) === false) {
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
100
admin/phpMyAdmin/libraries/classes/Command/SetVersionCommand.php
Normal file
100
admin/phpMyAdmin/libraries/classes/Command/SetVersionCommand.php
Normal file
|
@ -0,0 +1,100 @@
|
|||
<?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;
|
||||
|
||||
use const VERSION_SUFFIX;
|
||||
|
||||
/**
|
||||
* This class is generated by scripts/console.
|
||||
*
|
||||
* @see \PhpMyAdmin\Command\SetVersionCommand
|
||||
*/
|
||||
final class Version
|
||||
{
|
||||
// The VERSION_SUFFIX constant is defined at libraries/constants.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)) {
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$output->writeln('PhpMyAdmin\Version class successfully generated!');
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
271
admin/phpMyAdmin/libraries/classes/Command/TwigLintCommand.php
Normal file
271
admin/phpMyAdmin/libraries/classes/Command/TwigLintCommand.php
Normal file
|
@ -0,0 +1,271 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Command;
|
||||
|
||||
use PhpMyAdmin\Template;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Twig\Error\Error;
|
||||
use Twig\Loader\ArrayLoader;
|
||||
use Twig\Source;
|
||||
|
||||
use function array_push;
|
||||
use function closedir;
|
||||
use function count;
|
||||
use function explode;
|
||||
use function file_get_contents;
|
||||
use function is_dir;
|
||||
use function is_file;
|
||||
use function max;
|
||||
use function min;
|
||||
use function opendir;
|
||||
use function preg_match;
|
||||
use function readdir;
|
||||
use function restore_error_handler;
|
||||
use function set_error_handler;
|
||||
use function sprintf;
|
||||
|
||||
use const DIRECTORY_SEPARATOR;
|
||||
use const E_USER_DEPRECATED;
|
||||
|
||||
/**
|
||||
* Command that will validate your template syntax and output encountered errors.
|
||||
* Author: Marc Weistroff <marc.weistroff@sensiolabs.com>
|
||||
* Author: Jérôme Tamarelle <jerome@tamarelle.net>
|
||||
*
|
||||
* Copyright (c) 2013-2021 Fabien Potencier
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is furnished
|
||||
* to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
class TwigLintCommand extends Command
|
||||
{
|
||||
/** @var string|null */
|
||||
protected static $defaultName = 'lint:twig';
|
||||
|
||||
/** @var string|null */
|
||||
protected static $defaultDescription = 'Lint a Twig template and outputs encountered errors';
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->setDescription((string) self::$defaultDescription)
|
||||
->addOption('show-deprecations', null, InputOption::VALUE_NONE, 'Show deprecations as errors');
|
||||
}
|
||||
|
||||
protected function findFiles(string $baseFolder): array
|
||||
{
|
||||
/* Open the handle */
|
||||
$handle = @opendir($baseFolder);
|
||||
if ($handle === false) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$foundFiles = [];
|
||||
|
||||
while (($file = readdir($handle)) !== false) {
|
||||
if ($file === '.' || $file === '..') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$itemPath = $baseFolder . DIRECTORY_SEPARATOR . $file;
|
||||
|
||||
if (is_dir($itemPath)) {
|
||||
array_push($foundFiles, ...$this->findFiles($itemPath));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (! is_file($itemPath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$foundFiles[] = $itemPath;
|
||||
}
|
||||
|
||||
/* Close the handle */
|
||||
closedir($handle);
|
||||
|
||||
return $foundFiles;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$showDeprecations = $input->getOption('show-deprecations');
|
||||
|
||||
if ($showDeprecations) {
|
||||
$prevErrorHandler = set_error_handler(
|
||||
static function (int $level, string $message, string $file, int $line) use (&$prevErrorHandler) {
|
||||
if ($level === E_USER_DEPRECATED) {
|
||||
$templateLine = 0;
|
||||
if (preg_match('/ at line (\d+)[ .]/', $message, $matches)) {
|
||||
$templateLine = (int) $matches[1];
|
||||
}
|
||||
|
||||
throw new Error($message, $templateLine);
|
||||
}
|
||||
|
||||
return $prevErrorHandler ? $prevErrorHandler($level, $message, $file, $line) : false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
$filesInfo = $this->getFilesInfo(ROOT_PATH . 'templates');
|
||||
} finally {
|
||||
if ($showDeprecations) {
|
||||
restore_error_handler();
|
||||
}
|
||||
}
|
||||
|
||||
return $this->display($output, $io, $filesInfo);
|
||||
}
|
||||
|
||||
protected function getFilesInfo(string $templatesPath): array
|
||||
{
|
||||
$filesInfo = [];
|
||||
$filesFound = $this->findFiles($templatesPath);
|
||||
foreach ($filesFound as $file) {
|
||||
$filesInfo[] = $this->validate($this->getTemplateContents($file), $file);
|
||||
}
|
||||
|
||||
return $filesInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows easier testing
|
||||
*/
|
||||
protected function getTemplateContents(string $filePath): string
|
||||
{
|
||||
return (string) file_get_contents($filePath);
|
||||
}
|
||||
|
||||
private function validate(string $template, string $file): array
|
||||
{
|
||||
$twig = Template::getTwigEnvironment(null);
|
||||
|
||||
$realLoader = $twig->getLoader();
|
||||
try {
|
||||
$temporaryLoader = new ArrayLoader([$file => $template]);
|
||||
$twig->setLoader($temporaryLoader);
|
||||
$nodeTree = $twig->parse($twig->tokenize(new Source($template, $file)));
|
||||
$twig->compile($nodeTree);
|
||||
$twig->setLoader($realLoader);
|
||||
} catch (Error $e) {
|
||||
$twig->setLoader($realLoader);
|
||||
|
||||
return [
|
||||
'template' => $template,
|
||||
'file' => $file,
|
||||
'line' => $e->getTemplateLine(),
|
||||
'valid' => false,
|
||||
'exception' => $e,
|
||||
];
|
||||
}
|
||||
|
||||
return ['template' => $template, 'file' => $file, 'valid' => true];
|
||||
}
|
||||
|
||||
private function display(OutputInterface $output, SymfonyStyle $io, array $filesInfo): int
|
||||
{
|
||||
$errors = 0;
|
||||
|
||||
foreach ($filesInfo as $info) {
|
||||
if ($info['valid'] && $output->isVerbose()) {
|
||||
$io->comment('<info>OK</info>' . ($info['file'] ? sprintf(' in %s', $info['file']) : ''));
|
||||
} elseif (! $info['valid']) {
|
||||
++$errors;
|
||||
$this->renderException($io, $info['template'], $info['exception'], $info['file']);
|
||||
}
|
||||
}
|
||||
|
||||
if ($errors === 0) {
|
||||
$io->success(sprintf('All %d Twig files contain valid syntax.', count($filesInfo)));
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
$io->warning(
|
||||
sprintf(
|
||||
'%d Twig files have valid syntax and %d contain errors.',
|
||||
count($filesInfo) - $errors,
|
||||
$errors
|
||||
)
|
||||
);
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
private function renderException(
|
||||
SymfonyStyle $output,
|
||||
string $template,
|
||||
Error $exception,
|
||||
?string $file = null
|
||||
): void {
|
||||
$line = $exception->getTemplateLine();
|
||||
|
||||
if ($file) {
|
||||
$output->text(sprintf('<error> ERROR </error> in %s (line %s)', $file, $line));
|
||||
} else {
|
||||
$output->text(sprintf('<error> ERROR </error> (line %s)', $line));
|
||||
}
|
||||
|
||||
// If the line is not known (this might happen for deprecations if we fail at detecting the line for instance),
|
||||
// we render the message without context, to ensure the message is displayed.
|
||||
if ($line <= 0) {
|
||||
$output->text(sprintf('<error> >> %s</error> ', $exception->getRawMessage()));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->getContext($template, $line) as $lineNumber => $code) {
|
||||
$output->text(sprintf(
|
||||
'%s %-6s %s',
|
||||
$lineNumber === $line ? '<error> >> </error>' : ' ',
|
||||
$lineNumber,
|
||||
$code
|
||||
));
|
||||
if ($lineNumber !== $line) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$output->text(sprintf('<error> >> %s</error> ', $exception->getRawMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
private function getContext(string $template, int $line, int $context = 3): array
|
||||
{
|
||||
$lines = explode("\n", $template);
|
||||
|
||||
$position = max(0, $line - $context);
|
||||
$max = min(count($lines), $line - 1 + $context);
|
||||
|
||||
$result = [];
|
||||
while ($position < $max) {
|
||||
$result[$position + 1] = $lines[$position];
|
||||
++$position;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Command;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
use function file_put_contents;
|
||||
use function is_string;
|
||||
use function shell_exec;
|
||||
use function sprintf;
|
||||
use function str_replace;
|
||||
use function trim;
|
||||
|
||||
class WriteGitRevisionCommand extends Command
|
||||
{
|
||||
/** @var string */
|
||||
protected static $defaultName = 'write-revision-info';
|
||||
|
||||
/** @var string */
|
||||
private static $generatedClassTemplate = <<<'PHP'
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is generated by scripts/console.
|
||||
*
|
||||
* @see \PhpMyAdmin\Command\WriteGitRevisionCommand
|
||||
*/
|
||||
return [
|
||||
'revision' => '%s',
|
||||
'revisionUrl' => '%s',
|
||||
'branch' => '%s',
|
||||
'branchUrl' => '%s',
|
||||
];
|
||||
|
||||
PHP;
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->setDescription('Write Git revision');
|
||||
$this->addOption(
|
||||
'remote-commit-url',
|
||||
null,
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
'The remote URL to a commit',
|
||||
'https://github.com/phpmyadmin/phpmyadmin/commit/%s'
|
||||
);
|
||||
$this->addOption(
|
||||
'remote-branch-url',
|
||||
null,
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
'The remote URL to a branch',
|
||||
'https://github.com/phpmyadmin/phpmyadmin/tree/%s'
|
||||
);
|
||||
$this->setHelp('This command generates the revision-info.php file from Git data.');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
/** @var string $commitUrlFormat */
|
||||
$commitUrlFormat = $input->getOption('remote-commit-url');
|
||||
/** @var string $branchUrlFormat */
|
||||
$branchUrlFormat = $input->getOption('remote-branch-url');
|
||||
|
||||
$generatedClass = $this->getRevisionInfo($commitUrlFormat, $branchUrlFormat);
|
||||
if ($generatedClass === null) {
|
||||
$output->writeln('No revision information detected.');
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
if (! $this->writeGeneratedFile($generatedClass)) {
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$output->writeln('revision-info.php successfully generated!');
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
private function getRevisionInfo(string $commitUrlFormat, string $branchUrlFormat): ?string
|
||||
{
|
||||
$revisionText = $this->gitCli('describe --always');
|
||||
if ($revisionText === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$commitHash = $this->gitCli('log -1 --format="%H"');
|
||||
if ($commitHash === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$branchName = $this->gitCli('symbolic-ref -q HEAD') ?? $this->gitCli('name-rev --name-only HEAD 2>/dev/null');
|
||||
if ($branchName === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$branchName = trim(str_replace('refs/heads/', '', $branchName));
|
||||
|
||||
return sprintf(
|
||||
self::$generatedClassTemplate,
|
||||
trim($revisionText),
|
||||
sprintf($commitUrlFormat, trim($commitHash)),
|
||||
trim($branchName),
|
||||
sprintf($branchUrlFormat, $branchName)
|
||||
);
|
||||
}
|
||||
|
||||
protected function gitCli(string $command): ?string
|
||||
{
|
||||
/** @psalm-suppress ForbiddenCode */
|
||||
$output = shell_exec('git ' . $command);
|
||||
|
||||
return is_string($output) ? $output : null;
|
||||
}
|
||||
|
||||
private function writeGeneratedFile(string $generatedClass): bool
|
||||
{
|
||||
$result = file_put_contents(ROOT_PATH . 'revision-info.php', $generatedClass);
|
||||
|
||||
return $result !== false;
|
||||
}
|
||||
}
|
623
admin/phpMyAdmin/libraries/classes/Common.php
Normal file
623
admin/phpMyAdmin/libraries/classes/Common.php
Normal file
|
@ -0,0 +1,623 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin;
|
||||
|
||||
use PhpMyAdmin\ConfigStorage\Relation;
|
||||
use PhpMyAdmin\Dbal\DatabaseName;
|
||||
use PhpMyAdmin\Dbal\TableName;
|
||||
use PhpMyAdmin\Http\Factory\ServerRequestFactory;
|
||||
use PhpMyAdmin\Http\ServerRequest;
|
||||
use PhpMyAdmin\Plugins\AuthenticationPlugin;
|
||||
use PhpMyAdmin\SqlParser\Lexer;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Webmozart\Assert\Assert;
|
||||
use Webmozart\Assert\InvalidArgumentException;
|
||||
|
||||
use function __;
|
||||
use function array_pop;
|
||||
use function count;
|
||||
use function date_default_timezone_get;
|
||||
use function date_default_timezone_set;
|
||||
use function define;
|
||||
use function defined;
|
||||
use function explode;
|
||||
use function extension_loaded;
|
||||
use function function_exists;
|
||||
use function hash_equals;
|
||||
use function htmlspecialchars;
|
||||
use function implode;
|
||||
use function ini_get;
|
||||
use function ini_set;
|
||||
use function is_array;
|
||||
use function is_scalar;
|
||||
use function mb_internal_encoding;
|
||||
use function mb_strlen;
|
||||
use function mb_strpos;
|
||||
use function mb_strrpos;
|
||||
use function mb_substr;
|
||||
use function register_shutdown_function;
|
||||
use function session_id;
|
||||
use function str_replace;
|
||||
use function strlen;
|
||||
use function trigger_error;
|
||||
use function urldecode;
|
||||
|
||||
use const E_USER_ERROR;
|
||||
|
||||
final class Common
|
||||
{
|
||||
/**
|
||||
* Misc stuff and REQUIRED by ALL the scripts.
|
||||
* MUST be included by every script
|
||||
*
|
||||
* Among other things, it contains the advanced authentication work.
|
||||
*
|
||||
* Order of sections:
|
||||
*
|
||||
* the authentication libraries must be before the connection to db
|
||||
*
|
||||
* ... so the required order is:
|
||||
*
|
||||
* LABEL_variables_init
|
||||
* - initialize some variables always needed
|
||||
* LABEL_parsing_config_file
|
||||
* - parsing of the configuration file
|
||||
* LABEL_loading_language_file
|
||||
* - loading language file
|
||||
* LABEL_setup_servers
|
||||
* - check and setup configured servers
|
||||
* LABEL_theme_setup
|
||||
* - setting up themes
|
||||
*
|
||||
* - load of MySQL extension (if necessary)
|
||||
* - loading of an authentication library
|
||||
* - db connection
|
||||
* - authentication work
|
||||
*/
|
||||
public static function run(): void
|
||||
{
|
||||
global $containerBuilder, $errorHandler, $config, $server, $dbi, $request;
|
||||
global $lang, $cfg, $isConfigLoading, $auth_plugin, $route, $theme;
|
||||
global $urlParams, $isMinimumCommon, $sql_query, $token_mismatch;
|
||||
|
||||
$request = ServerRequestFactory::createFromGlobals();
|
||||
|
||||
$route = Routing::getCurrentRoute();
|
||||
|
||||
if ($route === '/import-status') {
|
||||
$isMinimumCommon = true;
|
||||
}
|
||||
|
||||
$containerBuilder = Core::getContainerBuilder();
|
||||
|
||||
/** @var ErrorHandler $errorHandler */
|
||||
$errorHandler = $containerBuilder->get('error_handler');
|
||||
|
||||
self::checkRequiredPhpExtensions();
|
||||
self::configurePhpSettings();
|
||||
self::cleanupPathInfo();
|
||||
|
||||
/* parsing configuration file LABEL_parsing_config_file */
|
||||
|
||||
/** @var bool $isConfigLoading Indication for the error handler */
|
||||
$isConfigLoading = false;
|
||||
|
||||
register_shutdown_function([Config::class, 'fatalErrorHandler']);
|
||||
|
||||
/**
|
||||
* Force reading of config file, because we removed sensitive values
|
||||
* in the previous iteration.
|
||||
*
|
||||
* @var Config $config
|
||||
*/
|
||||
$config = $containerBuilder->get('config');
|
||||
|
||||
/**
|
||||
* include session handling after the globals, to prevent overwriting
|
||||
*/
|
||||
if (! defined('PMA_NO_SESSION')) {
|
||||
Session::setUp($config, $errorHandler);
|
||||
}
|
||||
|
||||
$request = Core::populateRequestWithEncryptedQueryParams($request);
|
||||
|
||||
/**
|
||||
* init some variables LABEL_variables_init
|
||||
*/
|
||||
|
||||
/**
|
||||
* holds parameters to be passed to next page
|
||||
*
|
||||
* @global array $urlParams
|
||||
*/
|
||||
$urlParams = [];
|
||||
$containerBuilder->setParameter('url_params', $urlParams);
|
||||
|
||||
self::setGotoAndBackGlobals($containerBuilder, $config);
|
||||
self::checkTokenRequestParam();
|
||||
self::setDatabaseAndTableFromRequest($containerBuilder, $request);
|
||||
|
||||
/**
|
||||
* SQL query to be executed
|
||||
*
|
||||
* @global string $sql_query
|
||||
*/
|
||||
$sql_query = '';
|
||||
if ($request->isPost()) {
|
||||
$sql_query = $request->getParsedBodyParam('sql_query', '');
|
||||
}
|
||||
|
||||
$containerBuilder->setParameter('sql_query', $sql_query);
|
||||
|
||||
//$_REQUEST['set_theme'] // checked later in this file LABEL_theme_setup
|
||||
//$_REQUEST['server']; // checked later in this file
|
||||
//$_REQUEST['lang']; // checked by LABEL_loading_language_file
|
||||
|
||||
/* loading language file LABEL_loading_language_file */
|
||||
|
||||
/**
|
||||
* lang detection is done here
|
||||
*/
|
||||
$language = LanguageManager::getInstance()->selectLanguage();
|
||||
$language->activate();
|
||||
|
||||
/**
|
||||
* check for errors occurred while loading configuration
|
||||
* this check is done here after loading language files to present errors in locale
|
||||
*/
|
||||
$config->checkPermissions();
|
||||
$config->checkErrors();
|
||||
|
||||
self::checkServerConfiguration();
|
||||
self::checkRequest();
|
||||
|
||||
/* setup servers LABEL_setup_servers */
|
||||
|
||||
$config->checkServers();
|
||||
|
||||
/**
|
||||
* current server
|
||||
*
|
||||
* @global integer $server
|
||||
*/
|
||||
$server = $config->selectServer();
|
||||
$urlParams['server'] = $server;
|
||||
$containerBuilder->setParameter('server', $server);
|
||||
$containerBuilder->setParameter('url_params', $urlParams);
|
||||
|
||||
$cfg = $config->settings;
|
||||
|
||||
/* setup themes LABEL_theme_setup */
|
||||
|
||||
$theme = ThemeManager::initializeTheme();
|
||||
|
||||
/** @var DatabaseInterface $dbi */
|
||||
$dbi = null;
|
||||
|
||||
if (isset($isMinimumCommon)) {
|
||||
$config->loadUserPreferences();
|
||||
$containerBuilder->set('theme_manager', ThemeManager::getInstance());
|
||||
Tracker::enable();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* save some settings in cookies
|
||||
*
|
||||
* @todo should be done in PhpMyAdmin\Config
|
||||
*/
|
||||
$config->setCookie('pma_lang', (string) $lang);
|
||||
|
||||
ThemeManager::getInstance()->setThemeCookie();
|
||||
|
||||
$dbi = DatabaseInterface::load();
|
||||
$containerBuilder->set(DatabaseInterface::class, $dbi);
|
||||
$containerBuilder->setAlias('dbi', DatabaseInterface::class);
|
||||
|
||||
if (! empty($cfg['Server'])) {
|
||||
$config->getLoginCookieValidityFromCache($server);
|
||||
|
||||
$auth_plugin = Plugins::getAuthPlugin();
|
||||
$auth_plugin->authenticate();
|
||||
|
||||
/* Enable LOAD DATA LOCAL INFILE for LDI plugin */
|
||||
if ($route === '/import' && ($_POST['format'] ?? '') === 'ldi') {
|
||||
// Switch this before the DB connection is done
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
define('PMA_ENABLE_LDI', 1);
|
||||
// phpcs:enable
|
||||
}
|
||||
|
||||
self::connectToDatabaseServer($dbi, $auth_plugin);
|
||||
|
||||
$auth_plugin->rememberCredentials();
|
||||
|
||||
$auth_plugin->checkTwoFactor();
|
||||
|
||||
/* Log success */
|
||||
Logging::logUser($cfg['Server']['user']);
|
||||
|
||||
if ($dbi->getVersion() < $cfg['MysqlMinVersion']['internal']) {
|
||||
Core::fatalError(
|
||||
__('You should upgrade to %s %s or later.'),
|
||||
[
|
||||
'MySQL',
|
||||
$cfg['MysqlMinVersion']['human'],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// Sets the default delimiter (if specified).
|
||||
$sqlDelimiter = $request->getParam('sql_delimiter', '');
|
||||
if (strlen($sqlDelimiter) > 0) {
|
||||
// phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
Lexer::$DEFAULT_DELIMITER = $sqlDelimiter;
|
||||
}
|
||||
|
||||
// TODO: Set SQL modes too.
|
||||
} else { // end server connecting
|
||||
$response = ResponseRenderer::getInstance();
|
||||
$response->getHeader()->disableMenuAndConsole();
|
||||
$response->getFooter()->setMinimal();
|
||||
}
|
||||
|
||||
$response = ResponseRenderer::getInstance();
|
||||
|
||||
/**
|
||||
* There is no point in even attempting to process
|
||||
* an ajax request if there is a token mismatch
|
||||
*/
|
||||
if ($response->isAjax() && $request->isPost() && $token_mismatch) {
|
||||
$response->setRequestStatus(false);
|
||||
$response->addJSON(
|
||||
'message',
|
||||
Message::error(__('Error: Token mismatch'))
|
||||
);
|
||||
exit;
|
||||
}
|
||||
|
||||
Profiling::check($dbi, $response);
|
||||
|
||||
$containerBuilder->set('response', ResponseRenderer::getInstance());
|
||||
|
||||
// load user preferences
|
||||
$config->loadUserPreferences();
|
||||
|
||||
$containerBuilder->set('theme_manager', ThemeManager::getInstance());
|
||||
|
||||
/* Tell tracker that it can actually work */
|
||||
Tracker::enable();
|
||||
|
||||
if (empty($server) || ! isset($cfg['ZeroConf']) || $cfg['ZeroConf'] !== true) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var Relation $relation */
|
||||
$relation = $containerBuilder->get('relation');
|
||||
$dbi->postConnectControl($relation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that required PHP extensions are there.
|
||||
*/
|
||||
private static function checkRequiredPhpExtensions(): void
|
||||
{
|
||||
/**
|
||||
* Warning about mbstring.
|
||||
*/
|
||||
if (! function_exists('mb_detect_encoding')) {
|
||||
Core::warnMissingExtension('mbstring');
|
||||
}
|
||||
|
||||
/**
|
||||
* We really need this one!
|
||||
*/
|
||||
if (! function_exists('preg_replace')) {
|
||||
Core::warnMissingExtension('pcre', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON is required in several places.
|
||||
*/
|
||||
if (! function_exists('json_encode')) {
|
||||
Core::warnMissingExtension('json', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* ctype is required for Twig.
|
||||
*/
|
||||
if (! function_exists('ctype_alpha')) {
|
||||
Core::warnMissingExtension('ctype', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* hash is required for cookie authentication.
|
||||
*/
|
||||
if (function_exists('hash_hmac')) {
|
||||
return;
|
||||
}
|
||||
|
||||
Core::warnMissingExtension('hash', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies changes to PHP configuration.
|
||||
*/
|
||||
private static function configurePhpSettings(): void
|
||||
{
|
||||
/**
|
||||
* Set utf-8 encoding for PHP
|
||||
*/
|
||||
ini_set('default_charset', 'utf-8');
|
||||
mb_internal_encoding('utf-8');
|
||||
|
||||
/**
|
||||
* Set precision to sane value, with higher values
|
||||
* things behave slightly unexpectedly, for example
|
||||
* round(1.2, 2) returns 1.199999999999999956.
|
||||
*/
|
||||
ini_set('precision', '14');
|
||||
|
||||
/**
|
||||
* check timezone setting
|
||||
* this could produce an E_WARNING - but only once,
|
||||
* if not done here it will produce E_WARNING on every date/time function
|
||||
*/
|
||||
date_default_timezone_set(@date_default_timezone_get());
|
||||
}
|
||||
|
||||
/**
|
||||
* PATH_INFO could be compromised if set, so remove it from PHP_SELF
|
||||
* and provide a clean PHP_SELF here
|
||||
*/
|
||||
public static function cleanupPathInfo(): void
|
||||
{
|
||||
global $PMA_PHP_SELF;
|
||||
|
||||
$PMA_PHP_SELF = Core::getenv('PHP_SELF');
|
||||
if (empty($PMA_PHP_SELF)) {
|
||||
$PMA_PHP_SELF = urldecode(Core::getenv('REQUEST_URI'));
|
||||
}
|
||||
|
||||
$_PATH_INFO = Core::getenv('PATH_INFO');
|
||||
if (! empty($_PATH_INFO) && ! empty($PMA_PHP_SELF)) {
|
||||
$question_pos = mb_strpos($PMA_PHP_SELF, '?');
|
||||
if ($question_pos != false) {
|
||||
$PMA_PHP_SELF = mb_substr($PMA_PHP_SELF, 0, $question_pos);
|
||||
}
|
||||
|
||||
$path_info_pos = mb_strrpos($PMA_PHP_SELF, $_PATH_INFO);
|
||||
if ($path_info_pos !== false) {
|
||||
$path_info_part = mb_substr($PMA_PHP_SELF, $path_info_pos, mb_strlen($_PATH_INFO));
|
||||
if ($path_info_part == $_PATH_INFO) {
|
||||
$PMA_PHP_SELF = mb_substr($PMA_PHP_SELF, 0, $path_info_pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$path = [];
|
||||
foreach (explode('/', $PMA_PHP_SELF) as $part) {
|
||||
// ignore parts that have no value
|
||||
if (empty($part) || $part === '.') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($part !== '..') {
|
||||
// cool, we found a new part
|
||||
$path[] = $part;
|
||||
} elseif (count($path) > 0) {
|
||||
// going back up? sure
|
||||
array_pop($path);
|
||||
}
|
||||
|
||||
// Here we intentionall ignore case where we go too up
|
||||
// as there is nothing sane to do
|
||||
}
|
||||
|
||||
$PMA_PHP_SELF = htmlspecialchars('/' . implode('/', $path));
|
||||
}
|
||||
|
||||
private static function setGotoAndBackGlobals(ContainerInterface $container, Config $config): void
|
||||
{
|
||||
global $goto, $back, $urlParams;
|
||||
|
||||
// Holds page that should be displayed.
|
||||
$goto = '';
|
||||
$container->setParameter('goto', $goto);
|
||||
|
||||
if (isset($_REQUEST['goto']) && Core::checkPageValidity($_REQUEST['goto'])) {
|
||||
$goto = $_REQUEST['goto'];
|
||||
$urlParams['goto'] = $goto;
|
||||
$container->setParameter('goto', $goto);
|
||||
$container->setParameter('url_params', $urlParams);
|
||||
} else {
|
||||
if ($config->issetCookie('goto')) {
|
||||
$config->removeCookie('goto');
|
||||
}
|
||||
|
||||
unset($_REQUEST['goto'], $_GET['goto'], $_POST['goto']);
|
||||
}
|
||||
|
||||
if (isset($_REQUEST['back']) && Core::checkPageValidity($_REQUEST['back'])) {
|
||||
// Returning page.
|
||||
$back = $_REQUEST['back'];
|
||||
$container->setParameter('back', $back);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($config->issetCookie('back')) {
|
||||
$config->removeCookie('back');
|
||||
}
|
||||
|
||||
unset($_REQUEST['back'], $_GET['back'], $_POST['back']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether user supplied token is valid, if not remove any possibly
|
||||
* dangerous stuff from request.
|
||||
*
|
||||
* Check for token mismatch only if the Request method is POST.
|
||||
* GET Requests would never have token and therefore checking
|
||||
* mis-match does not make sense.
|
||||
*/
|
||||
public static function checkTokenRequestParam(): void
|
||||
{
|
||||
global $token_mismatch, $token_provided;
|
||||
|
||||
$token_mismatch = true;
|
||||
$token_provided = false;
|
||||
|
||||
if (($_SERVER['REQUEST_METHOD'] ?? 'GET') !== 'POST') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($_POST['token']) && is_scalar($_POST['token']) && strlen((string) $_POST['token']) > 0) {
|
||||
$token_provided = true;
|
||||
$token_mismatch = ! @hash_equals($_SESSION[' PMA_token '], (string) $_POST['token']);
|
||||
}
|
||||
|
||||
if (! $token_mismatch) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Warn in case the mismatch is result of failed setting of session cookie
|
||||
if (isset($_POST['set_session']) && $_POST['set_session'] !== session_id()) {
|
||||
trigger_error(
|
||||
__(
|
||||
'Failed to set session cookie. Maybe you are using HTTP instead of HTTPS to access phpMyAdmin.'
|
||||
),
|
||||
E_USER_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* We don't allow any POST operation parameters if the token is mismatched
|
||||
* or is not provided.
|
||||
*/
|
||||
$allowList = ['ajax_request'];
|
||||
Sanitize::removeRequestVars($allowList);
|
||||
}
|
||||
|
||||
private static function setDatabaseAndTableFromRequest(
|
||||
ContainerInterface $containerBuilder,
|
||||
ServerRequest $request
|
||||
): void {
|
||||
global $db, $table, $urlParams;
|
||||
|
||||
try {
|
||||
$db = DatabaseName::fromValue($request->getParam('db'))->getName();
|
||||
} catch (InvalidArgumentException $exception) {
|
||||
$db = '';
|
||||
}
|
||||
|
||||
try {
|
||||
Assert::stringNotEmpty($db);
|
||||
$table = TableName::fromValue($request->getParam('table'))->getName();
|
||||
} catch (InvalidArgumentException $exception) {
|
||||
$table = '';
|
||||
}
|
||||
|
||||
if (! is_array($urlParams)) {
|
||||
$urlParams = [];
|
||||
}
|
||||
|
||||
$urlParams['db'] = $db;
|
||||
$urlParams['table'] = $table;
|
||||
// If some parameter value includes the % character, you need to escape it by adding
|
||||
// another % so Symfony doesn't consider it a reference to a parameter name.
|
||||
$containerBuilder->setParameter('db', str_replace('%', '%%', $db));
|
||||
$containerBuilder->setParameter('table', str_replace('%', '%%', $table));
|
||||
$containerBuilder->setParameter('url_params', $urlParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether PHP configuration matches our needs.
|
||||
*/
|
||||
private static function checkServerConfiguration(): void
|
||||
{
|
||||
/**
|
||||
* As we try to handle charsets by ourself, mbstring overloads just
|
||||
* break it, see bug 1063821.
|
||||
*
|
||||
* We specifically use empty here as we are looking for anything else than
|
||||
* empty value or 0.
|
||||
*/
|
||||
if (extension_loaded('mbstring') && ! empty(ini_get('mbstring.func_overload'))) {
|
||||
Core::fatalError(
|
||||
__(
|
||||
'You have enabled mbstring.func_overload in your PHP '
|
||||
. 'configuration. This option is incompatible with phpMyAdmin '
|
||||
. 'and might cause some data to be corrupted!'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The ini_set and ini_get functions can be disabled using
|
||||
* disable_functions but we're relying quite a lot of them.
|
||||
*/
|
||||
if (function_exists('ini_get') && function_exists('ini_set')) {
|
||||
return;
|
||||
}
|
||||
|
||||
Core::fatalError(
|
||||
__(
|
||||
'The ini_get and/or ini_set functions are disabled in php.ini. phpMyAdmin requires these functions!'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks request and fails with fatal error if something problematic is found
|
||||
*/
|
||||
private static function checkRequest(): void
|
||||
{
|
||||
if (isset($_REQUEST['GLOBALS']) || isset($_FILES['GLOBALS'])) {
|
||||
Core::fatalError(__('GLOBALS overwrite attempt'));
|
||||
}
|
||||
|
||||
/**
|
||||
* protect against possible exploits - there is no need to have so much variables
|
||||
*/
|
||||
if (count($_REQUEST) <= 1000) {
|
||||
return;
|
||||
}
|
||||
|
||||
Core::fatalError(__('possible exploit'));
|
||||
}
|
||||
|
||||
private static function connectToDatabaseServer(DatabaseInterface $dbi, AuthenticationPlugin $auth): void
|
||||
{
|
||||
global $cfg;
|
||||
|
||||
/**
|
||||
* Try to connect MySQL with the control user profile (will be used to get the privileges list for the current
|
||||
* user but the true user link must be open after this one so it would be default one for all the scripts).
|
||||
*/
|
||||
$controlLink = false;
|
||||
if ($cfg['Server']['controluser'] !== '') {
|
||||
$controlLink = $dbi->connect(DatabaseInterface::CONNECT_CONTROL);
|
||||
}
|
||||
|
||||
// Connects to the server (validates user's login)
|
||||
$userLink = $dbi->connect(DatabaseInterface::CONNECT_USER);
|
||||
|
||||
if ($userLink === false) {
|
||||
$auth->showFailure('mysql-denied');
|
||||
}
|
||||
|
||||
if ($controlLink) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open separate connection for control queries, this is needed to avoid problems with table locking used in
|
||||
* main connection and phpMyAdmin issuing queries to configuration storage, which is not locked by that time.
|
||||
*/
|
||||
$dbi->connect(DatabaseInterface::CONNECT_USER, null, DatabaseInterface::CONNECT_CONTROL);
|
||||
}
|
||||
}
|
1396
admin/phpMyAdmin/libraries/classes/Config.php
Normal file
1396
admin/phpMyAdmin/libraries/classes/Config.php
Normal file
File diff suppressed because it is too large
Load diff
522
admin/phpMyAdmin/libraries/classes/Config/ConfigFile.php
Normal file
522
admin/phpMyAdmin/libraries/classes/Config/ConfigFile.php
Normal file
|
@ -0,0 +1,522 @@
|
|||
<?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 count;
|
||||
use function is_array;
|
||||
use function preg_replace;
|
||||
|
||||
/**
|
||||
* Config file management class.
|
||||
* Stores its data in $_SESSION
|
||||
*/
|
||||
class ConfigFile
|
||||
{
|
||||
/**
|
||||
* Stores default phpMyAdmin config
|
||||
*
|
||||
* @see Settings
|
||||
*
|
||||
* @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;
|
||||
|
||||
/**
|
||||
* @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
|
||||
$settings = new Settings([]);
|
||||
$this->defaultCfg = $settings->toArray();
|
||||
|
||||
// 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, $this->defaultCfg, $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
|
||||
*/
|
||||
public function setPersistKeys(array $keys): void
|
||||
{
|
||||
// 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
|
||||
*/
|
||||
public function setAllowedKeys($keys): void
|
||||
{
|
||||
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"
|
||||
*/
|
||||
public function setCfgUpdateReadMapping(array $mapping): void
|
||||
{
|
||||
$this->cfgUpdateReadMapping = $mapping;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets configuration data
|
||||
*/
|
||||
public function resetConfigData(): void
|
||||
{
|
||||
$_SESSION[$this->id] = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets configuration data (overrides old data)
|
||||
*
|
||||
* @param array $cfg Configuration options
|
||||
*/
|
||||
public function setConfigData(array $cfg): void
|
||||
{
|
||||
$_SESSION[$this->id] = $cfg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets config value
|
||||
*
|
||||
* @param string $path Path
|
||||
* @param mixed $value Value
|
||||
* @param string $canonicalPath Canonical path
|
||||
*/
|
||||
public function set($path, $value, $canonicalPath = null): void
|
||||
{
|
||||
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').
|
||||
*
|
||||
* @param array $array Multidimensional array
|
||||
* @param string $prefix Prefix
|
||||
*/
|
||||
private function getFlatArray(array $array, string $prefix = ''): array
|
||||
{
|
||||
$result = [];
|
||||
foreach ($array as $key => $value) {
|
||||
if (is_array($value) && ! isset($value[0])) {
|
||||
$result += $this->getFlatArray($value, $prefix . $key . '/');
|
||||
} else {
|
||||
$result[$prefix . $key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns default config in a flattened array
|
||||
*/
|
||||
public function getFlatDefaultConfig(): array
|
||||
{
|
||||
return $this->getFlatArray($this->defaultCfg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates config with values read from given array
|
||||
* (config will contain differences to defaults from {@see \PhpMyAdmin\Config\Settings}).
|
||||
*
|
||||
* @param array $cfg Configuration
|
||||
*/
|
||||
public function updateWithGlobalConfig(array $cfg): void
|
||||
{
|
||||
// load config array and flatten it
|
||||
$flatConfig = $this->getFlatArray($cfg);
|
||||
|
||||
// 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 {@see \PhpMyAdmin\Config\Settings} ($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
|
||||
*/
|
||||
public function getServers(): array
|
||||
{
|
||||
return $_SESSION[$this->id]['Servers'] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public function removeServer($server): void
|
||||
{
|
||||
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()
|
||||
{
|
||||
$c = $this->getFlatArray($_SESSION[$this->id]);
|
||||
|
||||
$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;
|
||||
}
|
||||
}
|
1015
admin/phpMyAdmin/libraries/classes/Config/Descriptions.php
Normal file
1015
admin/phpMyAdmin/libraries/classes/Config/Descriptions.php
Normal file
File diff suppressed because it is too large
Load diff
312
admin/phpMyAdmin/libraries/classes/Config/Form.php
Normal file
312
admin/phpMyAdmin/libraries/classes/Config/Form.php
Normal file
|
@ -0,0 +1,312 @@
|
|||
<?php
|
||||
/**
|
||||
* Form handling code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config;
|
||||
|
||||
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;
|
||||
|
||||
use const E_USER_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) {
|
||||
/** @var array $value */
|
||||
$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
|
||||
*/
|
||||
private function readFormPathsCallback($value, $key, $prefix): void
|
||||
{
|
||||
if (is_array($value)) {
|
||||
$prefix .= $key . '/';
|
||||
array_walk(
|
||||
$value,
|
||||
function ($value, $key, $prefix): void {
|
||||
$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
|
||||
*/
|
||||
protected function readFormPaths(array $form): void
|
||||
{
|
||||
// flatten form fields' paths and save them to $fields
|
||||
$this->fields = [];
|
||||
array_walk(
|
||||
$form,
|
||||
function ($value, $key, $prefix): void {
|
||||
$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
|
||||
*/
|
||||
protected function readTypes(): void
|
||||
{
|
||||
$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
|
||||
*/
|
||||
public function loadForm($formName, array $form): void
|
||||
{
|
||||
$this->name = $formName;
|
||||
$form = $this->cleanGroupPaths($form);
|
||||
$this->readFormPaths($form);
|
||||
$this->readTypes();
|
||||
}
|
||||
}
|
889
admin/phpMyAdmin/libraries/classes/Config/FormDisplay.php
Normal file
889
admin/phpMyAdmin/libraries/classes/Config/FormDisplay.php
Normal file
|
@ -0,0 +1,889 @@
|
|||
<?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 function __;
|
||||
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;
|
||||
|
||||
use const E_USER_WARNING;
|
||||
|
||||
/**
|
||||
* 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 = [];
|
||||
|
||||
/**
|
||||
* 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['config']);
|
||||
$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
|
||||
*/
|
||||
public function registerForm($formName, array $form, $serverId = null): void
|
||||
{
|
||||
$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']
|
||||
*/
|
||||
public function process($allowPartialSave = true, $checkFormSubmit = true): bool
|
||||
{
|
||||
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
|
||||
*/
|
||||
private function validate(): void
|
||||
{
|
||||
if ($this->isValidated) {
|
||||
return;
|
||||
}
|
||||
|
||||
$paths = [];
|
||||
$values = [];
|
||||
foreach ($this->forms as $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 forms
|
||||
*
|
||||
* @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(
|
||||
$showButtons = true,
|
||||
$formAction = null,
|
||||
$hiddenFields = null
|
||||
) {
|
||||
$js = [];
|
||||
$jsDefault = [];
|
||||
|
||||
/**
|
||||
* We do validation on page refresh when browser remembers field values,
|
||||
* add a field with known value which will be used for checks.
|
||||
*/
|
||||
static $hasCheckPageRefresh = false;
|
||||
if (! $hasCheckPageRefresh) {
|
||||
$hasCheckPageRefresh = true;
|
||||
}
|
||||
|
||||
$tabs = [];
|
||||
foreach ($this->forms as $form) {
|
||||
$tabs[$form->name] = Descriptions::get('Form_' . $form->name);
|
||||
}
|
||||
|
||||
// validate only when we aren't displaying a "new server" form
|
||||
$isNewServer = false;
|
||||
foreach ($this->forms as $form) {
|
||||
if ($form->index === 0) {
|
||||
$isNewServer = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (! $isNewServer) {
|
||||
$this->validate();
|
||||
}
|
||||
|
||||
// user preferences
|
||||
$this->loadUserprefsInfo();
|
||||
|
||||
$validators = Validator::getValidators($this->configFile);
|
||||
$forms = [];
|
||||
|
||||
foreach ($this->forms as $key => $form) {
|
||||
$this->formDisplayTemplate->group = 0;
|
||||
$forms[$key] = [
|
||||
'name' => $form->name,
|
||||
'descriptions' => [
|
||||
'name' => Descriptions::get('Form_' . $form->name, 'name'),
|
||||
'desc' => Descriptions::get('Form_' . $form->name, 'desc'),
|
||||
],
|
||||
'errors' => $this->errors[$form->name] ?? null,
|
||||
'fields_html' => '',
|
||||
];
|
||||
|
||||
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
|
||||
$forms[$key]['fields_html'] .= $this->displayFieldInput(
|
||||
$form,
|
||||
$field,
|
||||
$path,
|
||||
$workPath,
|
||||
$translatedPath,
|
||||
true,
|
||||
$userPrefsAllow,
|
||||
$jsDefault
|
||||
);
|
||||
// register JS validators for this field
|
||||
if (! isset($validators[$path])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->formDisplayTemplate->addJsValidate($translatedPath, $validators[$path], $js);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->formDisplayTemplate->display([
|
||||
'action' => $formAction,
|
||||
'has_check_page_refresh' => $hasCheckPageRefresh,
|
||||
'hidden_fields' => (array) $hiddenFields,
|
||||
'tabs' => $tabs,
|
||||
'forms' => $forms,
|
||||
'show_buttons' => $showButtons,
|
||||
'js_array' => $js,
|
||||
'js_default' => $jsDefault,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public function fixErrors(): void
|
||||
{
|
||||
$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 (array_keys($allowed) as $vk) {
|
||||
// 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
|
||||
*/
|
||||
public function save($forms, $allowPartialSave = true): bool
|
||||
{
|
||||
$result = true;
|
||||
$forms = (array) $forms;
|
||||
|
||||
$values = [];
|
||||
$toSave = [];
|
||||
$isSetupScript = $GLOBALS['config']->get('is_setup');
|
||||
if ($isSetupScript) {
|
||||
$this->loadUserprefsInfo();
|
||||
}
|
||||
|
||||
$this->errors = [];
|
||||
foreach ($forms as $formName) {
|
||||
if (! isset($this->forms[$formName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$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
|
||||
*/
|
||||
public function hasErrors(): bool
|
||||
{
|
||||
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}
|
||||
*/
|
||||
private function loadUserprefsInfo(): void
|
||||
{
|
||||
if ($this->userprefsKeys !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->userprefsKeys = array_flip(UserFormList::getFields());
|
||||
// read real config for user preferences display
|
||||
$userPrefsDisallow = $GLOBALS['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
|
||||
*/
|
||||
private function setComments($systemPath, array &$opts): void
|
||||
{
|
||||
// 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['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
|
||||
*/
|
||||
private function fillPostArrayParameters(array $postValues, $key): void
|
||||
{
|
||||
foreach ($postValues as $v) {
|
||||
$v = Util::requestString($v);
|
||||
if ($v === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$_POST[$key][] = $v;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,177 @@
|
|||
<?php
|
||||
/**
|
||||
* Form templates
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config;
|
||||
|
||||
use PhpMyAdmin\Config;
|
||||
use PhpMyAdmin\Sanitize;
|
||||
use PhpMyAdmin\Template;
|
||||
|
||||
use function array_shift;
|
||||
use function implode;
|
||||
|
||||
/**
|
||||
* 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 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 {
|
||||
$isSetupScript = $this->config->get('is_setup');
|
||||
$optionIsDisabled = ! $isSetupScript && isset($opts['userprefs_allow']) && ! $opts['userprefs_allow'];
|
||||
$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;
|
||||
}
|
||||
|
||||
return $this->template->render('config/form_display/input', [
|
||||
'is_setup' => $isSetupScript,
|
||||
'allows_customization' => $opts['userprefs_allow'] ?? null,
|
||||
'path' => $path,
|
||||
'has_errors' => isset($opts['errors']) && ! empty($opts['errors']),
|
||||
'errors' => $opts['errors'] ?? [],
|
||||
'show_restore_default' => $opts['show_restore_default'] ?? null,
|
||||
'set_value' => $opts['setvalue'] ?? null,
|
||||
'tr_class' => $trClass,
|
||||
'name' => $name,
|
||||
'doc' => $opts['doc'] ?? '',
|
||||
'option_is_disabled' => $optionIsDisabled,
|
||||
'description' => $description,
|
||||
'comment' => $opts['userprefs_comment'] ?? null,
|
||||
'type' => $type,
|
||||
'value' => $value,
|
||||
'value_is_default' => $valueIsDefault,
|
||||
'select_values' => $opts['values'] ?? [],
|
||||
'select_values_disabled' => $opts['values_disabled'] ?? [],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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--;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 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,
|
||||
]);
|
||||
}
|
||||
|
||||
public function display(array $data): string
|
||||
{
|
||||
return $this->template->render('config/form_display/display', $data);
|
||||
}
|
||||
}
|
86
admin/phpMyAdmin/libraries/classes/Config/Forms/BaseForm.php
Normal file
86
admin/phpMyAdmin/libraries/classes/Config/Forms/BaseForm.php
Normal file
|
@ -0,0 +1,86 @@
|
|||
<?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
|
||||
*/
|
||||
final 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 '';
|
||||
}
|
||||
}
|
155
admin/phpMyAdmin/libraries/classes/Config/Forms/BaseFormList.php
Normal file
155
admin/phpMyAdmin/libraries/classes/Config/Forms/BaseFormList.php
Normal file
|
@ -0,0 +1,155 @@
|
|||
<?php
|
||||
/**
|
||||
* User preferences form
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config\Forms;
|
||||
|
||||
use PhpMyAdmin\Config\ConfigFile;
|
||||
|
||||
use function array_merge;
|
||||
use function class_exists;
|
||||
use function in_array;
|
||||
|
||||
class BaseFormList
|
||||
{
|
||||
/**
|
||||
* List of all forms
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected static $all = [];
|
||||
|
||||
/** @var string */
|
||||
protected static $ns = 'PhpMyAdmin\\Config\\Forms\\';
|
||||
|
||||
/** @var array */
|
||||
private $forms;
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public static function getAll()
|
||||
{
|
||||
return static::$all;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name Name
|
||||
*/
|
||||
public static function isValid($name): bool
|
||||
{
|
||||
return in_array($name, static::$all);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name Name
|
||||
*
|
||||
* @return string|null
|
||||
* @psalm-return class-string<BaseForm>|null
|
||||
*/
|
||||
public static function get($name)
|
||||
{
|
||||
if (static::isValid($name)) {
|
||||
/** @var class-string<BaseForm> $class */
|
||||
$class = static::$ns . $name . 'Form';
|
||||
|
||||
return $class;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ConfigFile $cf Config file instance
|
||||
*/
|
||||
final public function __construct(ConfigFile $cf)
|
||||
{
|
||||
$this->forms = [];
|
||||
foreach (static::$all as $form) {
|
||||
$class = (string) static::get($form);
|
||||
if (! class_exists($class)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$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']
|
||||
*/
|
||||
public function process($allowPartialSave = true, $checkFormSubmit = true): bool
|
||||
{
|
||||
$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
|
||||
*/
|
||||
public function fixErrors(): void
|
||||
{
|
||||
foreach ($this->forms as $form) {
|
||||
$form->fixErrors();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells whether form validation failed
|
||||
*/
|
||||
public function hasErrors(): bool
|
||||
{
|
||||
$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 = (string) static::get($form);
|
||||
if (! class_exists($class)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$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 string[] */
|
||||
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,113 @@
|
|||
<?php
|
||||
/**
|
||||
* User preferences form
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config\Forms\Setup;
|
||||
|
||||
use PhpMyAdmin\Config\Forms\BaseForm;
|
||||
|
||||
use function __;
|
||||
|
||||
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 string[] */
|
||||
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,155 @@
|
|||
<?php
|
||||
/**
|
||||
* User preferences form
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config\Forms\User;
|
||||
|
||||
use PhpMyAdmin\Config\Forms\BaseForm;
|
||||
|
||||
use function __;
|
||||
|
||||
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,92 @@
|
|||
<?php
|
||||
/**
|
||||
* User preferences form
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config\Forms\User;
|
||||
|
||||
use PhpMyAdmin\Config\Forms\BaseForm;
|
||||
|
||||
use function __;
|
||||
|
||||
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,69 @@
|
|||
<?php
|
||||
/**
|
||||
* User preferences form
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config\Forms\User;
|
||||
|
||||
use PhpMyAdmin\Config\Forms\BaseForm;
|
||||
|
||||
use function __;
|
||||
|
||||
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,92 @@
|
|||
<?php
|
||||
/**
|
||||
* User preferences form
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config\Forms\User;
|
||||
|
||||
use PhpMyAdmin\Config\Forms\BaseForm;
|
||||
|
||||
use function __;
|
||||
|
||||
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,70 @@
|
|||
<?php
|
||||
/**
|
||||
* User preferences form
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config\Forms\User;
|
||||
|
||||
use PhpMyAdmin\Config\Forms\BaseForm;
|
||||
|
||||
use function __;
|
||||
|
||||
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,50 @@
|
|||
<?php
|
||||
/**
|
||||
* User preferences form
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config\Forms\User;
|
||||
|
||||
use PhpMyAdmin\Config\Forms\BaseForm;
|
||||
|
||||
use function __;
|
||||
|
||||
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 string[] */
|
||||
protected static $all = [
|
||||
'Features',
|
||||
'Sql',
|
||||
'Navi',
|
||||
'Main',
|
||||
'Export',
|
||||
'Import',
|
||||
];
|
||||
/** @var string */
|
||||
protected static $ns = 'PhpMyAdmin\\Config\\Forms\\User\\';
|
||||
}
|
193
admin/phpMyAdmin/libraries/classes/Config/PageSettings.php
Normal file
193
admin/phpMyAdmin/libraries/classes/Config/PageSettings.php
Normal file
|
@ -0,0 +1,193 @@
|
|||
<?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\ResponseRenderer;
|
||||
use PhpMyAdmin\UserPreferences;
|
||||
|
||||
use function __;
|
||||
|
||||
/**
|
||||
* 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['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
|
||||
*/
|
||||
private function processPageSettings(&$formDisplay, &$cf, &$error): void
|
||||
{
|
||||
if (! $formDisplay->process(false) || $formDisplay->hasErrors()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// save settings
|
||||
$result = $this->userPreferences->save($cf->getConfigArray());
|
||||
if ($result === true) {
|
||||
// reload page
|
||||
$response = ResponseRenderer::getInstance();
|
||||
Core::sendHeaderLocation(
|
||||
$response->getFooter()->getSelfUrl()
|
||||
);
|
||||
exit;
|
||||
}
|
||||
|
||||
$error = $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store errors in _errorHTML
|
||||
*
|
||||
* @param FormDisplay $formDisplay Form
|
||||
* @param Message|null $error Error message
|
||||
*/
|
||||
private function storeError(&$formDisplay, &$error): void
|
||||
{
|
||||
$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 = ResponseRenderer::getInstance();
|
||||
|
||||
$retval = '';
|
||||
|
||||
$this->storeError($formDisplay, $error);
|
||||
|
||||
$retval .= '<div id="' . $this->elemId . '">';
|
||||
$retval .= '<div class="page_settings">';
|
||||
$retval .= $formDisplay->getDisplay(
|
||||
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;
|
||||
}
|
||||
}
|
522
admin/phpMyAdmin/libraries/classes/Config/ServerConfigChecks.php
Normal file
522
admin/phpMyAdmin/libraries/classes/Config/ServerConfigChecks.php
Normal file
|
@ -0,0 +1,522 @@
|
|||
<?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 function __;
|
||||
use function function_exists;
|
||||
use function htmlspecialchars;
|
||||
use function ini_get;
|
||||
use function is_string;
|
||||
use function mb_strlen;
|
||||
use function sodium_crypto_secretbox_keygen;
|
||||
use function sprintf;
|
||||
|
||||
use const SODIUM_CRYPTO_SECRETBOX_KEYBYTES;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public function performConfigChecks(): void
|
||||
{
|
||||
$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();
|
||||
$isCookieAuthUsed = (int) $cookieAuthUsed;
|
||||
for ($i = 1; $i <= $serverCnt; $i++) {
|
||||
$cookieAuthServer = ($this->cfg->getValue('Servers/' . $i . '/auth_type') === 'cookie');
|
||||
$isCookieAuthUsed |= (int) $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 [
|
||||
(bool) $isCookieAuthUsed,
|
||||
$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
|
||||
&& (! is_string($blowfishSecret) || mb_strlen($blowfishSecret, '8bit') !== SODIUM_CRYPTO_SECRETBOX_KEYBYTES)
|
||||
) {
|
||||
$blowfishSecretSet = true;
|
||||
$this->cfg->set('blowfish_secret', sodium_crypto_secretbox_keygen());
|
||||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
protected function performConfigChecksZips(): void
|
||||
{
|
||||
$this->performConfigChecksServerGZipdump();
|
||||
$this->performConfigChecksServerBZipdump();
|
||||
$this->performConfigChecksServersZipdump();
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform config checks for zip part.
|
||||
*/
|
||||
protected function performConfigChecksServersZipdump(): void
|
||||
{
|
||||
// $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
|
||||
*/
|
||||
protected function performConfigChecksCookieAuthUsed(
|
||||
$cookieAuthUsed,
|
||||
$blowfishSecretSet,
|
||||
$blowfishSecret
|
||||
): void {
|
||||
// $cfg['blowfish_secret']
|
||||
// it's required for 'cookie' authentication
|
||||
if (! $cookieAuthUsed || ! $blowfishSecretSet) {
|
||||
return;
|
||||
}
|
||||
|
||||
// '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.'
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check configuration for login cookie
|
||||
*/
|
||||
protected function performConfigChecksLoginCookie(): void
|
||||
{
|
||||
// $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
|
||||
*/
|
||||
protected function performConfigChecksServerBZipdump(): void
|
||||
{
|
||||
// $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
|
||||
*/
|
||||
protected function performConfigChecksServerGZipdump(): void
|
||||
{
|
||||
// $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
|
||||
*/
|
||||
protected function functionExists($name): bool
|
||||
{
|
||||
return function_exists($name);
|
||||
}
|
||||
}
|
4596
admin/phpMyAdmin/libraries/classes/Config/Settings.php
Normal file
4596
admin/phpMyAdmin/libraries/classes/Config/Settings.php
Normal file
File diff suppressed because it is too large
Load diff
205
admin/phpMyAdmin/libraries/classes/Config/Settings/Console.php
Normal file
205
admin/phpMyAdmin/libraries/classes/Config/Settings/Console.php
Normal file
|
@ -0,0 +1,205 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config\Settings;
|
||||
|
||||
use function in_array;
|
||||
|
||||
// phpcs:disable Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
|
||||
/**
|
||||
* @psalm-immutable
|
||||
*/
|
||||
final class Console
|
||||
{
|
||||
/** @var bool */
|
||||
public $StartHistory;
|
||||
|
||||
/** @var bool */
|
||||
public $AlwaysExpand;
|
||||
|
||||
/** @var bool */
|
||||
public $CurrentQuery;
|
||||
|
||||
/** @var bool */
|
||||
public $EnterExecutes;
|
||||
|
||||
/** @var bool */
|
||||
public $DarkTheme;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @psalm-var 'info'|'show'|'collapse'
|
||||
*/
|
||||
public $Mode;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
* @psalm-var positive-int
|
||||
*/
|
||||
public $Height;
|
||||
|
||||
/** @var bool */
|
||||
public $GroupQueries;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @psalm-var 'exec'|'time'|'count'
|
||||
*/
|
||||
public $OrderBy;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @psalm-var 'asc'|'desc'
|
||||
*/
|
||||
public $Order;
|
||||
|
||||
/**
|
||||
* @param mixed[] $console
|
||||
*/
|
||||
public function __construct(array $console = [])
|
||||
{
|
||||
$this->StartHistory = $this->setStartHistory($console);
|
||||
$this->AlwaysExpand = $this->setAlwaysExpand($console);
|
||||
$this->CurrentQuery = $this->setCurrentQuery($console);
|
||||
$this->EnterExecutes = $this->setEnterExecutes($console);
|
||||
$this->DarkTheme = $this->setDarkTheme($console);
|
||||
$this->Mode = $this->setMode($console);
|
||||
$this->Height = $this->setHeight($console);
|
||||
$this->GroupQueries = $this->setGroupQueries($console);
|
||||
$this->OrderBy = $this->setOrderBy($console);
|
||||
$this->Order = $this->setOrder($console);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $console
|
||||
*/
|
||||
private function setStartHistory(array $console): bool
|
||||
{
|
||||
if (isset($console['StartHistory'])) {
|
||||
return (bool) $console['StartHistory'];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $console
|
||||
*/
|
||||
private function setAlwaysExpand(array $console): bool
|
||||
{
|
||||
if (isset($console['AlwaysExpand'])) {
|
||||
return (bool) $console['AlwaysExpand'];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $console
|
||||
*/
|
||||
private function setCurrentQuery(array $console): bool
|
||||
{
|
||||
if (isset($console['CurrentQuery'])) {
|
||||
return (bool) $console['CurrentQuery'];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $console
|
||||
*/
|
||||
private function setEnterExecutes(array $console): bool
|
||||
{
|
||||
if (isset($console['EnterExecutes'])) {
|
||||
return (bool) $console['EnterExecutes'];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $console
|
||||
*/
|
||||
private function setDarkTheme(array $console): bool
|
||||
{
|
||||
if (isset($console['DarkTheme'])) {
|
||||
return (bool) $console['DarkTheme'];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $console
|
||||
*
|
||||
* @psalm-return 'info'|'show'|'collapse'
|
||||
*/
|
||||
private function setMode(array $console): string
|
||||
{
|
||||
if (isset($console['Mode']) && in_array($console['Mode'], ['show', 'collapse'], true)) {
|
||||
return $console['Mode'];
|
||||
}
|
||||
|
||||
return 'info';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $console
|
||||
*
|
||||
* @psalm-return positive-int
|
||||
*/
|
||||
private function setHeight(array $console): int
|
||||
{
|
||||
if (isset($console['Height'])) {
|
||||
$height = (int) $console['Height'];
|
||||
if ($height >= 1) {
|
||||
return $height;
|
||||
}
|
||||
}
|
||||
|
||||
return 92;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $console
|
||||
*/
|
||||
private function setGroupQueries(array $console): bool
|
||||
{
|
||||
if (isset($console['GroupQueries'])) {
|
||||
return (bool) $console['GroupQueries'];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $console
|
||||
*
|
||||
* @psalm-return 'exec'|'time'|'count'
|
||||
*/
|
||||
private function setOrderBy(array $console): string
|
||||
{
|
||||
if (isset($console['OrderBy']) && in_array($console['OrderBy'], ['time', 'count'], true)) {
|
||||
return $console['OrderBy'];
|
||||
}
|
||||
|
||||
return 'exec';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $console
|
||||
*
|
||||
* @psalm-return 'asc'|'desc'
|
||||
*/
|
||||
private function setOrder(array $console): string
|
||||
{
|
||||
if (isset($console['Order']) && $console['Order'] === 'desc') {
|
||||
return 'desc';
|
||||
}
|
||||
|
||||
return 'asc';
|
||||
}
|
||||
}
|
82
admin/phpMyAdmin/libraries/classes/Config/Settings/Debug.php
Normal file
82
admin/phpMyAdmin/libraries/classes/Config/Settings/Debug.php
Normal file
|
@ -0,0 +1,82 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config\Settings;
|
||||
|
||||
/**
|
||||
* @psalm-immutable
|
||||
*/
|
||||
final class Debug
|
||||
{
|
||||
/**
|
||||
* Output executed queries and their execution times.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $sql;
|
||||
|
||||
/**
|
||||
* Log executed queries and their execution times to syslog.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $sqllog;
|
||||
|
||||
/**
|
||||
* Enable to let server present itself as demo server.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $demo;
|
||||
|
||||
/**
|
||||
* Enable Simple two-factor authentication.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $simple2fa;
|
||||
|
||||
/**
|
||||
* @param mixed[] $debug
|
||||
*/
|
||||
public function __construct(array $debug = [])
|
||||
{
|
||||
$this->sql = $this->setSql($debug);
|
||||
$this->sqllog = $this->setSqlLog($debug);
|
||||
$this->demo = $this->setDemo($debug);
|
||||
$this->simple2fa = $this->setSimple2fa($debug);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $debug
|
||||
*/
|
||||
private function setSql(array $debug): bool
|
||||
{
|
||||
return isset($debug['sql']) && $debug['sql'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $debug
|
||||
*/
|
||||
private function setSqlLog(array $debug): bool
|
||||
{
|
||||
return isset($debug['sqllog']) && $debug['sqllog'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $debug
|
||||
*/
|
||||
private function setDemo(array $debug): bool
|
||||
{
|
||||
return isset($debug['demo']) && $debug['demo'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $debug
|
||||
*/
|
||||
private function setSimple2fa(array $debug): bool
|
||||
{
|
||||
return isset($debug['simple2fa']) && $debug['simple2fa'];
|
||||
}
|
||||
}
|
2027
admin/phpMyAdmin/libraries/classes/Config/Settings/Export.php
Normal file
2027
admin/phpMyAdmin/libraries/classes/Config/Settings/Export.php
Normal file
File diff suppressed because it is too large
Load diff
489
admin/phpMyAdmin/libraries/classes/Config/Settings/Import.php
Normal file
489
admin/phpMyAdmin/libraries/classes/Config/Settings/Import.php
Normal file
|
@ -0,0 +1,489 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config\Settings;
|
||||
|
||||
// phpcs:disable Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
|
||||
use function in_array;
|
||||
|
||||
/**
|
||||
* @psalm-immutable
|
||||
*/
|
||||
final class Import
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
* @psalm-var 'csv'|'docsql'|'ldi'|'sql'
|
||||
*/
|
||||
public $format;
|
||||
|
||||
/**
|
||||
* Default charset for import.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $charset;
|
||||
|
||||
/** @var bool */
|
||||
public $allow_interrupt;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
* @psalm-var 0|positive-int
|
||||
*/
|
||||
public $skip_queries;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @psalm-var 'NONE'|'ANSI'|'DB2'|'MAXDB'|'MYSQL323'|'MYSQL40'|'MSSQL'|'ORACLE'|'TRADITIONAL'
|
||||
*/
|
||||
public $sql_compatibility;
|
||||
|
||||
/** @var bool */
|
||||
public $sql_no_auto_value_on_zero;
|
||||
|
||||
/** @var bool */
|
||||
public $sql_read_as_multibytes;
|
||||
|
||||
/** @var bool */
|
||||
public $csv_replace;
|
||||
|
||||
/** @var bool */
|
||||
public $csv_ignore;
|
||||
|
||||
/** @var string */
|
||||
public $csv_terminated;
|
||||
|
||||
/** @var string */
|
||||
public $csv_enclosed;
|
||||
|
||||
/** @var string */
|
||||
public $csv_escaped;
|
||||
|
||||
/** @var string */
|
||||
public $csv_new_line;
|
||||
|
||||
/** @var string */
|
||||
public $csv_columns;
|
||||
|
||||
/** @var bool */
|
||||
public $csv_col_names;
|
||||
|
||||
/** @var bool */
|
||||
public $ldi_replace;
|
||||
|
||||
/** @var bool */
|
||||
public $ldi_ignore;
|
||||
|
||||
/** @var string */
|
||||
public $ldi_terminated;
|
||||
|
||||
/** @var string */
|
||||
public $ldi_enclosed;
|
||||
|
||||
/** @var string */
|
||||
public $ldi_escaped;
|
||||
|
||||
/** @var string */
|
||||
public $ldi_new_line;
|
||||
|
||||
/** @var string */
|
||||
public $ldi_columns;
|
||||
|
||||
/**
|
||||
* 'auto' for auto-detection, true or false for forcing
|
||||
*
|
||||
* @var string|bool
|
||||
* @psalm-var 'auto'|bool
|
||||
*/
|
||||
public $ldi_local_option;
|
||||
|
||||
/** @var bool */
|
||||
public $ods_col_names;
|
||||
|
||||
/** @var bool */
|
||||
public $ods_empty_rows;
|
||||
|
||||
/** @var bool */
|
||||
public $ods_recognize_percentages;
|
||||
|
||||
/** @var bool */
|
||||
public $ods_recognize_currency;
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*/
|
||||
public function __construct(array $import = [])
|
||||
{
|
||||
$this->format = $this->setFormat($import);
|
||||
$this->charset = $this->setCharset($import);
|
||||
$this->allow_interrupt = $this->setAllowInterrupt($import);
|
||||
$this->skip_queries = $this->setSkipQueries($import);
|
||||
$this->sql_compatibility = $this->setSqlCompatibility($import);
|
||||
$this->sql_no_auto_value_on_zero = $this->setSqlNoAutoValueOnZero($import);
|
||||
$this->sql_read_as_multibytes = $this->setSqlReadAsMultibytes($import);
|
||||
$this->csv_replace = $this->setCsvReplace($import);
|
||||
$this->csv_ignore = $this->setCsvIgnore($import);
|
||||
$this->csv_terminated = $this->setCsvTerminated($import);
|
||||
$this->csv_enclosed = $this->setCsvEnclosed($import);
|
||||
$this->csv_escaped = $this->setCsvEscaped($import);
|
||||
$this->csv_new_line = $this->setCsvNewLine($import);
|
||||
$this->csv_columns = $this->setCsvColumns($import);
|
||||
$this->csv_col_names = $this->setCsvColNames($import);
|
||||
$this->ldi_replace = $this->setLdiReplace($import);
|
||||
$this->ldi_ignore = $this->setLdiIgnore($import);
|
||||
$this->ldi_terminated = $this->setLdiTerminated($import);
|
||||
$this->ldi_enclosed = $this->setLdiEnclosed($import);
|
||||
$this->ldi_escaped = $this->setLdiEscaped($import);
|
||||
$this->ldi_new_line = $this->setLdiNewLine($import);
|
||||
$this->ldi_columns = $this->setLdiColumns($import);
|
||||
$this->ldi_local_option = $this->setLdiLocalOption($import);
|
||||
$this->ods_col_names = $this->setOdsColNames($import);
|
||||
$this->ods_empty_rows = $this->setOdsEmptyRows($import);
|
||||
$this->ods_recognize_percentages = $this->setOdsRecognizePercentages($import);
|
||||
$this->ods_recognize_currency = $this->setOdsRecognizeCurrency($import);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*
|
||||
* @psalm-return 'csv'|'docsql'|'ldi'|'sql'
|
||||
*/
|
||||
private function setFormat(array $import): string
|
||||
{
|
||||
if (! isset($import['format']) || ! in_array($import['format'], ['csv', 'docsql', 'ldi'], true)) {
|
||||
return 'sql';
|
||||
}
|
||||
|
||||
return $import['format'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*/
|
||||
private function setCharset(array $import): string
|
||||
{
|
||||
if (! isset($import['charset'])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return (string) $import['charset'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*/
|
||||
private function setAllowInterrupt(array $import): bool
|
||||
{
|
||||
if (! isset($import['allow_interrupt'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (bool) $import['allow_interrupt'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*
|
||||
* @psalm-return 0|positive-int
|
||||
*/
|
||||
private function setSkipQueries(array $import): int
|
||||
{
|
||||
if (! isset($import['skip_queries'])) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$skipQueries = (int) $import['skip_queries'];
|
||||
|
||||
return $skipQueries >= 1 ? $skipQueries : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*
|
||||
* @psalm-return 'NONE'|'ANSI'|'DB2'|'MAXDB'|'MYSQL323'|'MYSQL40'|'MSSQL'|'ORACLE'|'TRADITIONAL'
|
||||
*/
|
||||
private function setSqlCompatibility(array $import): string
|
||||
{
|
||||
if (
|
||||
! isset($import['sql_compatibility']) || ! in_array(
|
||||
$import['sql_compatibility'],
|
||||
['ANSI', 'DB2', 'MAXDB', 'MYSQL323', 'MYSQL40', 'MSSQL', 'ORACLE', 'TRADITIONAL'],
|
||||
true
|
||||
)
|
||||
) {
|
||||
return 'NONE';
|
||||
}
|
||||
|
||||
return $import['sql_compatibility'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*/
|
||||
private function setSqlNoAutoValueOnZero(array $import): bool
|
||||
{
|
||||
if (! isset($import['sql_no_auto_value_on_zero'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (bool) $import['sql_no_auto_value_on_zero'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*/
|
||||
private function setSqlReadAsMultibytes(array $import): bool
|
||||
{
|
||||
if (! isset($import['sql_read_as_multibytes'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (bool) $import['sql_read_as_multibytes'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*/
|
||||
private function setCsvReplace(array $import): bool
|
||||
{
|
||||
if (! isset($import['csv_replace'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (bool) $import['csv_replace'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*/
|
||||
private function setCsvIgnore(array $import): bool
|
||||
{
|
||||
if (! isset($import['csv_ignore'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (bool) $import['csv_ignore'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*/
|
||||
private function setCsvTerminated(array $import): string
|
||||
{
|
||||
if (! isset($import['csv_terminated'])) {
|
||||
return ',';
|
||||
}
|
||||
|
||||
return (string) $import['csv_terminated'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*/
|
||||
private function setCsvEnclosed(array $import): string
|
||||
{
|
||||
if (! isset($import['csv_enclosed'])) {
|
||||
return '"';
|
||||
}
|
||||
|
||||
return (string) $import['csv_enclosed'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*/
|
||||
private function setCsvEscaped(array $import): string
|
||||
{
|
||||
if (! isset($import['csv_escaped'])) {
|
||||
return '"';
|
||||
}
|
||||
|
||||
return (string) $import['csv_escaped'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*/
|
||||
private function setCsvNewLine(array $import): string
|
||||
{
|
||||
if (! isset($import['csv_new_line'])) {
|
||||
return 'auto';
|
||||
}
|
||||
|
||||
return (string) $import['csv_new_line'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*/
|
||||
private function setCsvColumns(array $import): string
|
||||
{
|
||||
if (! isset($import['csv_columns'])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return (string) $import['csv_columns'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*/
|
||||
private function setCsvColNames(array $import): bool
|
||||
{
|
||||
if (! isset($import['csv_col_names'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (bool) $import['csv_col_names'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*/
|
||||
private function setLdiReplace(array $import): bool
|
||||
{
|
||||
if (! isset($import['ldi_replace'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (bool) $import['ldi_replace'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*/
|
||||
private function setLdiIgnore(array $import): bool
|
||||
{
|
||||
if (! isset($import['ldi_ignore'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (bool) $import['ldi_ignore'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*/
|
||||
private function setLdiTerminated(array $import): string
|
||||
{
|
||||
if (! isset($import['ldi_terminated'])) {
|
||||
return ';';
|
||||
}
|
||||
|
||||
return (string) $import['ldi_terminated'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*/
|
||||
private function setLdiEnclosed(array $import): string
|
||||
{
|
||||
if (! isset($import['ldi_enclosed'])) {
|
||||
return '"';
|
||||
}
|
||||
|
||||
return (string) $import['ldi_enclosed'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*/
|
||||
private function setLdiEscaped(array $import): string
|
||||
{
|
||||
if (! isset($import['ldi_escaped'])) {
|
||||
return '\\';
|
||||
}
|
||||
|
||||
return (string) $import['ldi_escaped'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*/
|
||||
private function setLdiNewLine(array $import): string
|
||||
{
|
||||
if (! isset($import['ldi_new_line'])) {
|
||||
return 'auto';
|
||||
}
|
||||
|
||||
return (string) $import['ldi_new_line'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*/
|
||||
private function setLdiColumns(array $import): string
|
||||
{
|
||||
if (! isset($import['ldi_columns'])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return (string) $import['ldi_columns'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*
|
||||
* @return bool|string
|
||||
* @psalm-return 'auto'|bool
|
||||
*/
|
||||
private function setLdiLocalOption(array $import)
|
||||
{
|
||||
if (! isset($import['ldi_local_option']) || $import['ldi_local_option'] === 'auto') {
|
||||
return 'auto';
|
||||
}
|
||||
|
||||
return (bool) $import['ldi_local_option'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*/
|
||||
private function setOdsColNames(array $import): bool
|
||||
{
|
||||
if (! isset($import['ods_col_names'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (bool) $import['ods_col_names'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*/
|
||||
private function setOdsEmptyRows(array $import): bool
|
||||
{
|
||||
if (! isset($import['ods_empty_rows'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (bool) $import['ods_empty_rows'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*/
|
||||
private function setOdsRecognizePercentages(array $import): bool
|
||||
{
|
||||
if (! isset($import['ods_recognize_percentages'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (bool) $import['ods_recognize_percentages'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $import
|
||||
*/
|
||||
private function setOdsRecognizeCurrency(array $import): bool
|
||||
{
|
||||
if (! isset($import['ods_recognize_currency'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (bool) $import['ods_recognize_currency'];
|
||||
}
|
||||
}
|
369
admin/phpMyAdmin/libraries/classes/Config/Settings/Schema.php
Normal file
369
admin/phpMyAdmin/libraries/classes/Config/Settings/Schema.php
Normal file
|
@ -0,0 +1,369 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config\Settings;
|
||||
|
||||
// phpcs:disable Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
|
||||
use function in_array;
|
||||
|
||||
/**
|
||||
* @psalm-immutable
|
||||
*/
|
||||
final class Schema
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
* @psalm-var 'pdf'|'eps'|'dia'|'svg'
|
||||
*/
|
||||
public $format;
|
||||
|
||||
/** @var bool */
|
||||
public $pdf_show_color;
|
||||
|
||||
/** @var bool */
|
||||
public $pdf_show_keys;
|
||||
|
||||
/** @var bool */
|
||||
public $pdf_all_tables_same_width;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @psalm-var 'L'|'P'
|
||||
*/
|
||||
public $pdf_orientation;
|
||||
|
||||
/** @var string */
|
||||
public $pdf_paper;
|
||||
|
||||
/** @var bool */
|
||||
public $pdf_show_grid;
|
||||
|
||||
/** @var bool */
|
||||
public $pdf_with_doc;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @psalm-var ''|'name_asc'|'name_desc'
|
||||
*/
|
||||
public $pdf_table_order;
|
||||
|
||||
/** @var bool */
|
||||
public $dia_show_color;
|
||||
|
||||
/** @var bool */
|
||||
public $dia_show_keys;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @psalm-var 'L'|'P'
|
||||
*/
|
||||
public $dia_orientation;
|
||||
|
||||
/** @var string */
|
||||
public $dia_paper;
|
||||
|
||||
/** @var bool */
|
||||
public $eps_show_color;
|
||||
|
||||
/** @var bool */
|
||||
public $eps_show_keys;
|
||||
|
||||
/** @var bool */
|
||||
public $eps_all_tables_same_width;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @psalm-var 'L'|'P'
|
||||
*/
|
||||
public $eps_orientation;
|
||||
|
||||
/** @var bool */
|
||||
public $svg_show_color;
|
||||
|
||||
/** @var bool */
|
||||
public $svg_show_keys;
|
||||
|
||||
/** @var bool */
|
||||
public $svg_all_tables_same_width;
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $schema
|
||||
*/
|
||||
public function __construct(array $schema = [])
|
||||
{
|
||||
$this->format = $this->setFormat($schema);
|
||||
$this->pdf_show_color = $this->setPdfShowColor($schema);
|
||||
$this->pdf_show_keys = $this->setPdfShowKeys($schema);
|
||||
$this->pdf_all_tables_same_width = $this->setPdfAllTablesSameWidth($schema);
|
||||
$this->pdf_orientation = $this->setPdfOrientation($schema);
|
||||
$this->pdf_paper = $this->setPdfPaper($schema);
|
||||
$this->pdf_show_grid = $this->setPdfShowGrid($schema);
|
||||
$this->pdf_with_doc = $this->setPdfWithDoc($schema);
|
||||
$this->pdf_table_order = $this->setPdfTableOrder($schema);
|
||||
$this->dia_show_color = $this->setDiaShowColor($schema);
|
||||
$this->dia_show_keys = $this->setDiaShowKeys($schema);
|
||||
$this->dia_orientation = $this->setDiaOrientation($schema);
|
||||
$this->dia_paper = $this->setDiaPaper($schema);
|
||||
$this->eps_show_color = $this->setEpsShowColor($schema);
|
||||
$this->eps_show_keys = $this->setEpsShowKeys($schema);
|
||||
$this->eps_all_tables_same_width = $this->setEpsAllTablesSameWidth($schema);
|
||||
$this->eps_orientation = $this->setEpsOrientation($schema);
|
||||
$this->svg_show_color = $this->setSvgShowColor($schema);
|
||||
$this->svg_show_keys = $this->setSvgShowKeys($schema);
|
||||
$this->svg_all_tables_same_width = $this->setSvgAllTablesSameWidth($schema);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $schema
|
||||
*
|
||||
* @psalm-return 'pdf'|'eps'|'dia'|'svg'
|
||||
*/
|
||||
private function setFormat(array $schema): string
|
||||
{
|
||||
if (isset($schema['format']) && in_array($schema['format'], ['eps', 'dia', 'svg'], true)) {
|
||||
return $schema['format'];
|
||||
}
|
||||
|
||||
return 'pdf';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $schema
|
||||
*/
|
||||
private function setPdfShowColor(array $schema): bool
|
||||
{
|
||||
if (isset($schema['pdf_show_color'])) {
|
||||
return (bool) $schema['pdf_show_color'];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $schema
|
||||
*/
|
||||
private function setPdfShowKeys(array $schema): bool
|
||||
{
|
||||
if (isset($schema['pdf_show_keys'])) {
|
||||
return (bool) $schema['pdf_show_keys'];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $schema
|
||||
*/
|
||||
private function setPdfAllTablesSameWidth(array $schema): bool
|
||||
{
|
||||
if (isset($schema['pdf_all_tables_same_width'])) {
|
||||
return (bool) $schema['pdf_all_tables_same_width'];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $schema
|
||||
*
|
||||
* @psalm-return 'L'|'P'
|
||||
*/
|
||||
private function setPdfOrientation(array $schema): string
|
||||
{
|
||||
if (isset($schema['pdf_orientation']) && $schema['pdf_orientation'] === 'P') {
|
||||
return 'P';
|
||||
}
|
||||
|
||||
return 'L';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $schema
|
||||
*/
|
||||
private function setPdfPaper(array $schema): string
|
||||
{
|
||||
if (isset($schema['pdf_paper'])) {
|
||||
return (string) $schema['pdf_paper'];
|
||||
}
|
||||
|
||||
return 'A4';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $schema
|
||||
*/
|
||||
private function setPdfShowGrid(array $schema): bool
|
||||
{
|
||||
if (isset($schema['pdf_show_grid'])) {
|
||||
return (bool) $schema['pdf_show_grid'];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $schema
|
||||
*/
|
||||
private function setPdfWithDoc(array $schema): bool
|
||||
{
|
||||
if (isset($schema['pdf_with_doc'])) {
|
||||
return (bool) $schema['pdf_with_doc'];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $schema
|
||||
*
|
||||
* @psalm-return ''|'name_asc'|'name_desc'
|
||||
*/
|
||||
private function setPdfTableOrder(array $schema): string
|
||||
{
|
||||
if (
|
||||
isset($schema['pdf_table_order']) && in_array($schema['pdf_table_order'], ['name_asc', 'name_desc'], true)
|
||||
) {
|
||||
return $schema['pdf_table_order'];
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $schema
|
||||
*/
|
||||
private function setDiaShowColor(array $schema): bool
|
||||
{
|
||||
if (isset($schema['dia_show_color'])) {
|
||||
return (bool) $schema['dia_show_color'];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $schema
|
||||
*/
|
||||
private function setDiaShowKeys(array $schema): bool
|
||||
{
|
||||
if (isset($schema['dia_show_keys'])) {
|
||||
return (bool) $schema['dia_show_keys'];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $schema
|
||||
*
|
||||
* @psalm-return 'L'|'P'
|
||||
*/
|
||||
private function setDiaOrientation(array $schema): string
|
||||
{
|
||||
if (isset($schema['dia_orientation']) && $schema['dia_orientation'] === 'P') {
|
||||
return 'P';
|
||||
}
|
||||
|
||||
return 'L';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $schema
|
||||
*/
|
||||
private function setDiaPaper(array $schema): string
|
||||
{
|
||||
if (isset($schema['dia_paper'])) {
|
||||
return (string) $schema['dia_paper'];
|
||||
}
|
||||
|
||||
return 'A4';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $schema
|
||||
*/
|
||||
private function setEpsShowColor(array $schema): bool
|
||||
{
|
||||
if (isset($schema['eps_show_color'])) {
|
||||
return (bool) $schema['eps_show_color'];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $schema
|
||||
*/
|
||||
private function setEpsShowKeys(array $schema): bool
|
||||
{
|
||||
if (isset($schema['eps_show_keys'])) {
|
||||
return (bool) $schema['eps_show_keys'];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $schema
|
||||
*/
|
||||
private function setEpsAllTablesSameWidth(array $schema): bool
|
||||
{
|
||||
if (isset($schema['eps_all_tables_same_width'])) {
|
||||
return (bool) $schema['eps_all_tables_same_width'];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $schema
|
||||
*
|
||||
* @psalm-return 'L'|'P'
|
||||
*/
|
||||
private function setEpsOrientation(array $schema): string
|
||||
{
|
||||
if (isset($schema['eps_orientation']) && $schema['eps_orientation'] === 'P') {
|
||||
return 'P';
|
||||
}
|
||||
|
||||
return 'L';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $schema
|
||||
*/
|
||||
private function setSvgShowColor(array $schema): bool
|
||||
{
|
||||
if (isset($schema['svg_show_color'])) {
|
||||
return (bool) $schema['svg_show_color'];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $schema
|
||||
*/
|
||||
private function setSvgShowKeys(array $schema): bool
|
||||
{
|
||||
if (isset($schema['svg_show_keys'])) {
|
||||
return (bool) $schema['svg_show_keys'];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $schema
|
||||
*/
|
||||
private function setSvgAllTablesSameWidth(array $schema): bool
|
||||
{
|
||||
if (isset($schema['svg_all_tables_same_width'])) {
|
||||
return (bool) $schema['svg_all_tables_same_width'];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
1398
admin/phpMyAdmin/libraries/classes/Config/Settings/Server.php
Normal file
1398
admin/phpMyAdmin/libraries/classes/Config/Settings/Server.php
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,84 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config\Settings;
|
||||
|
||||
// phpcs:disable Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
|
||||
/**
|
||||
* @psalm-immutable
|
||||
*/
|
||||
final class SqlQueryBox
|
||||
{
|
||||
/**
|
||||
* Display an "Edit" link on the results page to change a query.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $Edit;
|
||||
|
||||
/**
|
||||
* Display an "Explain SQL" link on the results page.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $Explain;
|
||||
|
||||
/**
|
||||
* Display a "Create PHP code" link on the results page to wrap a query in PHP.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $ShowAsPHP;
|
||||
|
||||
/**
|
||||
* Display a "Refresh" link on the results page.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $Refresh;
|
||||
|
||||
/**
|
||||
* @param mixed[] $sqlQueryBox
|
||||
*/
|
||||
public function __construct(array $sqlQueryBox = [])
|
||||
{
|
||||
$this->Edit = $this->setEdit($sqlQueryBox);
|
||||
$this->Explain = $this->setExplain($sqlQueryBox);
|
||||
$this->ShowAsPHP = $this->setShowAsPHP($sqlQueryBox);
|
||||
$this->Refresh = $this->setRefresh($sqlQueryBox);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $sqlQueryBox
|
||||
*/
|
||||
private function setEdit(array $sqlQueryBox): bool
|
||||
{
|
||||
return ! isset($sqlQueryBox['Edit']) || $sqlQueryBox['Edit'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $sqlQueryBox
|
||||
*/
|
||||
private function setExplain(array $sqlQueryBox): bool
|
||||
{
|
||||
return ! isset($sqlQueryBox['Explain']) || $sqlQueryBox['Explain'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $sqlQueryBox
|
||||
*/
|
||||
private function setShowAsPHP(array $sqlQueryBox): bool
|
||||
{
|
||||
return ! isset($sqlQueryBox['ShowAsPHP']) || $sqlQueryBox['ShowAsPHP'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $sqlQueryBox
|
||||
*/
|
||||
private function setRefresh(array $sqlQueryBox): bool
|
||||
{
|
||||
return ! isset($sqlQueryBox['Refresh']) || $sqlQueryBox['Refresh'];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,391 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config\Settings;
|
||||
|
||||
use function is_array;
|
||||
|
||||
// phpcs:disable Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
|
||||
/**
|
||||
* @psalm-immutable
|
||||
*/
|
||||
final class Transformations
|
||||
{
|
||||
/**
|
||||
* Displays a part of a string.
|
||||
* - The first option is the number of characters to skip from the beginning of the string (Default 0).
|
||||
* - The second option is the number of characters to return (Default: until end of string).
|
||||
* - The third option is the string to append and/or prepend when truncation occurs (Default: "…").
|
||||
*
|
||||
* @var array<int, int|string>
|
||||
* @psalm-var array{0: int, 1: 'all'|int, 2: string}
|
||||
*/
|
||||
public $Substring;
|
||||
|
||||
/**
|
||||
* Converts Boolean values to text (default 'T' and 'F').
|
||||
* - First option is for TRUE, second for FALSE. Nonzero=true.
|
||||
*
|
||||
* @var string[]
|
||||
* @psalm-var array{0: string, 1: string}
|
||||
*/
|
||||
public $Bool2Text;
|
||||
|
||||
/**
|
||||
* LINUX ONLY: Launches an external application and feeds it the column data via standard input.
|
||||
* Returns the standard output of the application. The default is Tidy, to pretty-print HTML code.
|
||||
* For security reasons, you have to manually edit the file
|
||||
* libraries/classes/Plugins/Transformations/Abs/ExternalTransformationsPlugin.php and list the tools
|
||||
* you want to make available.
|
||||
* - The first option is then the number of the program you want to use.
|
||||
* - The second option should be blank for historical reasons.
|
||||
* - The third option, if set to 1, will convert the output using htmlspecialchars() (Default 1).
|
||||
* - The fourth option, if set to 1, will prevent wrapping and ensure that the output appears
|
||||
* all on one line (Default 1).
|
||||
*
|
||||
* @var array<int, int|string>
|
||||
* @psalm-var array{0: int, 1: string, 2: int, 3: int}
|
||||
*/
|
||||
public $External;
|
||||
|
||||
/**
|
||||
* Prepends and/or Appends text to a string.
|
||||
* - First option is text to be prepended. second is appended (enclosed in single quotes, default empty string).
|
||||
*
|
||||
* @var string[]
|
||||
* @psalm-var array{0: string, 1: string}
|
||||
*/
|
||||
public $PreApPend;
|
||||
|
||||
/**
|
||||
* Displays hexadecimal representation of data.
|
||||
* Optional first parameter specifies how often space will be added (defaults to 2 nibbles).
|
||||
*
|
||||
* @var string[]
|
||||
* @psalm-var array{0: 0|positive-int}
|
||||
*/
|
||||
public $Hex;
|
||||
|
||||
/**
|
||||
* Displays a TIME, TIMESTAMP, DATETIME or numeric unix timestamp column as formatted date.
|
||||
* - The first option is the offset (in hours) which will be added to the timestamp (Default: 0).
|
||||
* - Use second option to specify a different date/time format string.
|
||||
* - Third option determines whether you want to see local date or UTC one (use "local" or "utc" strings) for that.
|
||||
* According to that, date format has different value - for "local" see the documentation
|
||||
* for PHP's strftime() function and for "utc" it is done using gmdate() function.
|
||||
*
|
||||
* @var array<int, int|string>
|
||||
* @psalm-var array{0: 0|positive-int, 1: string, 2: 'local'|'utc'}
|
||||
*/
|
||||
public $DateFormat;
|
||||
|
||||
/**
|
||||
* Displays a clickable thumbnail.
|
||||
* The options are the maximum width and height in pixels.
|
||||
* The original aspect ratio is preserved.
|
||||
*
|
||||
* @var array<(int|string), (int|string|array<string, string>|null)>
|
||||
* @psalm-var array{
|
||||
* 0: 0|positive-int,
|
||||
* 1: 0|positive-int,
|
||||
* wrapper_link: string|null,
|
||||
* wrapper_params: array<array-key, string>
|
||||
* }
|
||||
*/
|
||||
public $Inline;
|
||||
|
||||
/**
|
||||
* Displays an image and a link; the column contains the filename.
|
||||
* - The first option is a URL prefix like "https://www.example.com/".
|
||||
* - The second and third options are the width and the height in pixels.
|
||||
*
|
||||
* @var array<int, int|string|null>
|
||||
* @psalm-var array{0: string|null, 1: 0|positive-int, 2: 0|positive-int}
|
||||
*/
|
||||
public $TextImageLink;
|
||||
|
||||
/**
|
||||
* Displays a link; the column contains the filename.
|
||||
* - The first option is a URL prefix like "https://www.example.com/".
|
||||
* - The second option is a title for the link.
|
||||
*
|
||||
* @var array<int, string|null>
|
||||
* @psalm-var array{0: string|null, 1: string|null, 2: bool|null}
|
||||
*/
|
||||
public $TextLink;
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $transformations
|
||||
*/
|
||||
public function __construct(array $transformations = [])
|
||||
{
|
||||
$this->Substring = $this->setSubstring($transformations);
|
||||
$this->Bool2Text = $this->setBool2Text($transformations);
|
||||
$this->External = $this->setExternal($transformations);
|
||||
$this->PreApPend = $this->setPreApPend($transformations);
|
||||
$this->Hex = $this->setHex($transformations);
|
||||
$this->DateFormat = $this->setDateFormat($transformations);
|
||||
$this->Inline = $this->setInline($transformations);
|
||||
$this->TextImageLink = $this->setTextImageLink($transformations);
|
||||
$this->TextLink = $this->setTextLink($transformations);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $transformations
|
||||
*
|
||||
* @return array<int, int|string>
|
||||
* @psalm-return array{0: int, 1: 'all'|int, 2: string}
|
||||
*/
|
||||
private function setSubstring(array $transformations): array
|
||||
{
|
||||
$substring = [0, 'all', '…'];
|
||||
if (isset($transformations['Substring']) && is_array($transformations['Substring'])) {
|
||||
if (isset($transformations['Substring'][0])) {
|
||||
$substring[0] = (int) $transformations['Substring'][0];
|
||||
}
|
||||
|
||||
if (isset($transformations['Substring'][1]) && $transformations['Substring'][1] !== 'all') {
|
||||
$substring[1] = (int) $transformations['Substring'][1];
|
||||
}
|
||||
|
||||
if (isset($transformations['Substring'][2])) {
|
||||
$substring[2] = (string) $transformations['Substring'][2];
|
||||
}
|
||||
}
|
||||
|
||||
return $substring;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $transformations
|
||||
*
|
||||
* @return string[]
|
||||
* @psalm-return array{0: string, 1: string}
|
||||
*/
|
||||
private function setBool2Text(array $transformations): array
|
||||
{
|
||||
$bool2Text = ['T', 'F'];
|
||||
if (isset($transformations['Bool2Text']) && is_array($transformations['Bool2Text'])) {
|
||||
if (isset($transformations['Bool2Text'][0])) {
|
||||
$bool2Text[0] = (string) $transformations['Bool2Text'][0];
|
||||
}
|
||||
|
||||
if (isset($transformations['Bool2Text'][1])) {
|
||||
$bool2Text[1] = (string) $transformations['Bool2Text'][1];
|
||||
}
|
||||
}
|
||||
|
||||
return $bool2Text;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $transformations
|
||||
*
|
||||
* @return array<int, int|string>
|
||||
* @psalm-return array{0: int, 1: string, 2: int, 3: int}
|
||||
*/
|
||||
private function setExternal(array $transformations): array
|
||||
{
|
||||
$external = [0, '-f /dev/null -i -wrap -q', 1, 1];
|
||||
if (isset($transformations['External']) && is_array($transformations['External'])) {
|
||||
if (isset($transformations['External'][0])) {
|
||||
$external[0] = (int) $transformations['External'][0];
|
||||
}
|
||||
|
||||
if (isset($transformations['External'][1])) {
|
||||
$external[1] = (string) $transformations['External'][1];
|
||||
}
|
||||
|
||||
if (isset($transformations['External'][2])) {
|
||||
$external[2] = (int) $transformations['External'][2];
|
||||
}
|
||||
|
||||
if (isset($transformations['External'][3])) {
|
||||
$external[3] = (int) $transformations['External'][3];
|
||||
}
|
||||
}
|
||||
|
||||
return $external;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $transformations
|
||||
*
|
||||
* @return string[]
|
||||
* @psalm-return array{0: string, 1: string}
|
||||
*/
|
||||
private function setPreApPend(array $transformations): array
|
||||
{
|
||||
$preApPend = ['', ''];
|
||||
if (isset($transformations['PreApPend']) && is_array($transformations['PreApPend'])) {
|
||||
if (isset($transformations['PreApPend'][0])) {
|
||||
$preApPend[0] = (string) $transformations['PreApPend'][0];
|
||||
}
|
||||
|
||||
if (isset($transformations['PreApPend'][1])) {
|
||||
$preApPend[1] = (string) $transformations['PreApPend'][1];
|
||||
}
|
||||
}
|
||||
|
||||
return $preApPend;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $transformations
|
||||
*
|
||||
* @return string[]
|
||||
* @psalm-return array{0: 0|positive-int}
|
||||
*/
|
||||
private function setHex(array $transformations): array
|
||||
{
|
||||
if (isset($transformations['Hex']) && is_array($transformations['Hex'])) {
|
||||
if (isset($transformations['Hex'][0])) {
|
||||
$length = (int) $transformations['Hex'][0];
|
||||
if ($length >= 0) {
|
||||
return [$length];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [2];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $transformations
|
||||
*
|
||||
* @return array<int, int|string>
|
||||
* @psalm-return array{0: 0|positive-int, 1: string, 2: 'local'|'utc'}
|
||||
*/
|
||||
private function setDateFormat(array $transformations): array
|
||||
{
|
||||
$dateFormat = [0, '', 'local'];
|
||||
if (isset($transformations['DateFormat']) && is_array($transformations['DateFormat'])) {
|
||||
if (isset($transformations['DateFormat'][0])) {
|
||||
$offset = (int) $transformations['DateFormat'][0];
|
||||
if ($offset >= 1) {
|
||||
$dateFormat[0] = $offset;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($transformations['DateFormat'][1])) {
|
||||
$dateFormat[1] = (string) $transformations['DateFormat'][1];
|
||||
}
|
||||
|
||||
if (isset($transformations['DateFormat'][2]) && $transformations['DateFormat'][2] === 'utc') {
|
||||
$dateFormat[2] = 'utc';
|
||||
}
|
||||
}
|
||||
|
||||
return $dateFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $transformations
|
||||
*
|
||||
* @return array<(int|string), (int|string|array<string, string>|null)>
|
||||
* @psalm-return array{
|
||||
* 0: 0|positive-int,
|
||||
* 1: 0|positive-int,
|
||||
* wrapper_link: string|null,
|
||||
* wrapper_params: array<array-key, string>
|
||||
* }
|
||||
*/
|
||||
private function setInline(array $transformations): array
|
||||
{
|
||||
$inline = [100, 100, 'wrapper_link' => null, 'wrapper_params' => []];
|
||||
if (isset($transformations['Inline']) && is_array($transformations['Inline'])) {
|
||||
if (isset($transformations['Inline'][0])) {
|
||||
$width = (int) $transformations['Inline'][0];
|
||||
if ($width >= 0) {
|
||||
$inline[0] = $width;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($transformations['Inline'][1])) {
|
||||
$height = (int) $transformations['Inline'][1];
|
||||
if ($height >= 0) {
|
||||
$inline[1] = $height;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($transformations['Inline']['wrapper_link'])) {
|
||||
$inline['wrapper_link'] = (string) $transformations['Inline']['wrapper_link'];
|
||||
}
|
||||
|
||||
if (
|
||||
isset($transformations['Inline']['wrapper_params'])
|
||||
&& is_array($transformations['Inline']['wrapper_params'])
|
||||
) {
|
||||
/**
|
||||
* @var int|string $key
|
||||
* @var mixed $value
|
||||
*/
|
||||
foreach ($transformations['Inline']['wrapper_params'] as $key => $value) {
|
||||
$inline['wrapper_params'][$key] = (string) $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $inline;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $transformations
|
||||
*
|
||||
* @return array<int, int|string|null>
|
||||
* @psalm-return array{0: string|null, 1: 0|positive-int, 2: 0|positive-int}
|
||||
*/
|
||||
private function setTextImageLink(array $transformations): array
|
||||
{
|
||||
$textImageLink = [null, 100, 50];
|
||||
if (isset($transformations['TextImageLink']) && is_array($transformations['TextImageLink'])) {
|
||||
if (isset($transformations['TextImageLink'][0])) {
|
||||
$textImageLink[0] = (string) $transformations['TextImageLink'][0];
|
||||
}
|
||||
|
||||
if (isset($transformations['TextImageLink'][1])) {
|
||||
$width = (int) $transformations['TextImageLink'][1];
|
||||
if ($width >= 0) {
|
||||
$textImageLink[1] = $width;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($transformations['TextImageLink'][2])) {
|
||||
$height = (int) $transformations['TextImageLink'][2];
|
||||
if ($height >= 0) {
|
||||
$textImageLink[2] = $height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $textImageLink;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $transformations
|
||||
*
|
||||
* @return array<int, string|null>
|
||||
* @psalm-return array{0: string|null, 1: string|null, 2: bool|null}
|
||||
*/
|
||||
private function setTextLink(array $transformations): array
|
||||
{
|
||||
$textLink = [null, null, null];
|
||||
if (isset($transformations['TextLink']) && is_array($transformations['TextLink'])) {
|
||||
if (isset($transformations['TextLink'][0])) {
|
||||
$textLink[0] = (string) $transformations['TextLink'][0];
|
||||
}
|
||||
|
||||
if (isset($transformations['TextLink'][1])) {
|
||||
$textLink[1] = (string) $transformations['TextLink'][1];
|
||||
}
|
||||
|
||||
if (isset($transformations['TextLink'][2])) {
|
||||
$textLink[2] = (bool) $transformations['TextLink'][2];
|
||||
}
|
||||
}
|
||||
|
||||
return $textLink;
|
||||
}
|
||||
}
|
486
admin/phpMyAdmin/libraries/classes/Config/SpecialSchemaLinks.php
Normal file
486
admin/phpMyAdmin/libraries/classes/Config/SpecialSchemaLinks.php
Normal file
|
@ -0,0 +1,486 @@
|
|||
<?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;
|
||||
|
||||
$defaultPageDatabase = './' . Util::getScriptNameForOption($cfg['DefaultTabDatabase'], 'database');
|
||||
$defaultPageTable = './' . 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' => $defaultPageTable,
|
||||
],
|
||||
'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' => $defaultPageTable,
|
||||
],
|
||||
'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' => $defaultPageTable,
|
||||
],
|
||||
],
|
||||
'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' => $defaultPageTable,
|
||||
],
|
||||
],
|
||||
'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' => $defaultPageTable,
|
||||
],
|
||||
'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' => $defaultPageTable,
|
||||
],
|
||||
'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' => $defaultPageTable,
|
||||
],
|
||||
'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' => $defaultPageTable,
|
||||
],
|
||||
],
|
||||
'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' => $defaultPageTable,
|
||||
],
|
||||
'referenced_table_name' => [
|
||||
'link_param' => 'table',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'constraint_schema',
|
||||
],
|
||||
],
|
||||
'default_page' => $defaultPageTable,
|
||||
],
|
||||
],
|
||||
'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' => $defaultPageDatabase,
|
||||
],
|
||||
],
|
||||
'statistics' => [
|
||||
'table_name' => [
|
||||
'link_param' => 'table',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'table_schema',
|
||||
],
|
||||
],
|
||||
'default_page' => $defaultPageTable,
|
||||
],
|
||||
'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' => $defaultPageTable,
|
||||
],
|
||||
],
|
||||
'table_constraints' => [
|
||||
'table_name' => [
|
||||
'link_param' => 'table',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'table_schema',
|
||||
],
|
||||
],
|
||||
'default_page' => $defaultPageTable,
|
||||
],
|
||||
],
|
||||
'views' => [
|
||||
'table_name' => [
|
||||
'link_param' => 'table',
|
||||
'link_dependancy_params' => [
|
||||
0 => [
|
||||
'param_info' => 'db',
|
||||
'column_name' => 'table_schema',
|
||||
],
|
||||
],
|
||||
'default_page' => $defaultPageTable,
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
613
admin/phpMyAdmin/libraries/classes/Config/Validator.php
Normal file
613
admin/phpMyAdmin/libraries/classes/Config/Validator.php
Normal file
|
@ -0,0 +1,613 @@
|
|||
<?php
|
||||
/**
|
||||
* Form validation for configuration editor
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Config;
|
||||
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\Util;
|
||||
|
||||
use function __;
|
||||
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 mysqli_report;
|
||||
use function preg_match;
|
||||
use function preg_replace;
|
||||
use function sprintf;
|
||||
use function str_replace;
|
||||
use function trim;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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['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['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);
|
||||
/** @var callable $vname */
|
||||
$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] = [];
|
||||
}
|
||||
|
||||
$errorList = array_map('PhpMyAdmin\Sanitize::sanitizeMessage', (array) $errorList);
|
||||
$result[$key] = array_merge($result[$key], $errorList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// restore original paths
|
||||
$newResult = [];
|
||||
foreach ($result as $k => $v) {
|
||||
$k2 = $keyMap[$k] ?? $k;
|
||||
$newResult[$k2] = $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();
|
||||
|
||||
/** @var string $socket */
|
||||
$socket = empty($socket) ? null : $socket;
|
||||
/** @var int $port */
|
||||
$port = empty($port) ? null : (int) $port;
|
||||
|
||||
mysqli_report(MYSQLI_REPORT_OFF);
|
||||
|
||||
$conn = @mysqli_connect($host, $user, (string) $pass, '', $port, $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 (is_array($test)) {
|
||||
$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 (is_array($test)) {
|
||||
$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
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\ConfigStorage\Features;
|
||||
|
||||
use PhpMyAdmin\Dbal\DatabaseName;
|
||||
use PhpMyAdmin\Dbal\TableName;
|
||||
|
||||
/**
|
||||
* @psalm-immutable
|
||||
*/
|
||||
final class BookmarkFeature
|
||||
{
|
||||
/** @var DatabaseName */
|
||||
public $database;
|
||||
|
||||
/** @var TableName */
|
||||
public $bookmark;
|
||||
|
||||
public function __construct(DatabaseName $database, TableName $bookmark)
|
||||
{
|
||||
$this->database = $database;
|
||||
$this->bookmark = $bookmark;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\ConfigStorage\Features;
|
||||
|
||||
use PhpMyAdmin\Dbal\DatabaseName;
|
||||
use PhpMyAdmin\Dbal\TableName;
|
||||
|
||||
/**
|
||||
* @psalm-immutable
|
||||
*/
|
||||
final class BrowserTransformationFeature
|
||||
{
|
||||
/** @var DatabaseName */
|
||||
public $database;
|
||||
|
||||
/** @var TableName */
|
||||
public $columnInfo;
|
||||
|
||||
public function __construct(DatabaseName $database, TableName $columnInfo)
|
||||
{
|
||||
$this->database = $database;
|
||||
$this->columnInfo = $columnInfo;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\ConfigStorage\Features;
|
||||
|
||||
use PhpMyAdmin\Dbal\DatabaseName;
|
||||
use PhpMyAdmin\Dbal\TableName;
|
||||
|
||||
/**
|
||||
* @psalm-immutable
|
||||
*/
|
||||
final class CentralColumnsFeature
|
||||
{
|
||||
/** @var DatabaseName */
|
||||
public $database;
|
||||
|
||||
/** @var TableName */
|
||||
public $centralColumns;
|
||||
|
||||
public function __construct(DatabaseName $database, TableName $centralColumns)
|
||||
{
|
||||
$this->database = $database;
|
||||
$this->centralColumns = $centralColumns;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\ConfigStorage\Features;
|
||||
|
||||
use PhpMyAdmin\Dbal\DatabaseName;
|
||||
use PhpMyAdmin\Dbal\TableName;
|
||||
|
||||
/**
|
||||
* @psalm-immutable
|
||||
*/
|
||||
final class ColumnCommentsFeature
|
||||
{
|
||||
/** @var DatabaseName */
|
||||
public $database;
|
||||
|
||||
/** @var TableName */
|
||||
public $columnInfo;
|
||||
|
||||
public function __construct(DatabaseName $database, TableName $columnInfo)
|
||||
{
|
||||
$this->database = $database;
|
||||
$this->columnInfo = $columnInfo;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\ConfigStorage\Features;
|
||||
|
||||
use PhpMyAdmin\Dbal\DatabaseName;
|
||||
use PhpMyAdmin\Dbal\TableName;
|
||||
|
||||
/**
|
||||
* @psalm-immutable
|
||||
*/
|
||||
final class ConfigurableMenusFeature
|
||||
{
|
||||
/** @var DatabaseName */
|
||||
public $database;
|
||||
|
||||
/** @var TableName */
|
||||
public $userGroups;
|
||||
|
||||
/** @var TableName */
|
||||
public $users;
|
||||
|
||||
public function __construct(DatabaseName $database, TableName $userGroups, TableName $users)
|
||||
{
|
||||
$this->database = $database;
|
||||
$this->userGroups = $userGroups;
|
||||
$this->users = $users;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\ConfigStorage\Features;
|
||||
|
||||
use PhpMyAdmin\Dbal\DatabaseName;
|
||||
use PhpMyAdmin\Dbal\TableName;
|
||||
|
||||
/**
|
||||
* @psalm-immutable
|
||||
*/
|
||||
final class DatabaseDesignerSettingsFeature
|
||||
{
|
||||
/** @var DatabaseName */
|
||||
public $database;
|
||||
|
||||
/** @var TableName */
|
||||
public $designerSettings;
|
||||
|
||||
public function __construct(DatabaseName $database, TableName $designerSettings)
|
||||
{
|
||||
$this->database = $database;
|
||||
$this->designerSettings = $designerSettings;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\ConfigStorage\Features;
|
||||
|
||||
use PhpMyAdmin\Dbal\DatabaseName;
|
||||
use PhpMyAdmin\Dbal\TableName;
|
||||
|
||||
/**
|
||||
* @psalm-immutable
|
||||
*/
|
||||
final class DisplayFeature
|
||||
{
|
||||
/** @var DatabaseName */
|
||||
public $database;
|
||||
|
||||
/** @var TableName */
|
||||
public $relation;
|
||||
|
||||
/** @var TableName */
|
||||
public $tableInfo;
|
||||
|
||||
public function __construct(DatabaseName $database, TableName $relation, TableName $tableInfo)
|
||||
{
|
||||
$this->database = $database;
|
||||
$this->relation = $relation;
|
||||
$this->tableInfo = $tableInfo;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\ConfigStorage\Features;
|
||||
|
||||
use PhpMyAdmin\Dbal\DatabaseName;
|
||||
use PhpMyAdmin\Dbal\TableName;
|
||||
|
||||
/**
|
||||
* @psalm-immutable
|
||||
*/
|
||||
final class ExportTemplatesFeature
|
||||
{
|
||||
/** @var DatabaseName */
|
||||
public $database;
|
||||
|
||||
/** @var TableName */
|
||||
public $exportTemplates;
|
||||
|
||||
public function __construct(DatabaseName $database, TableName $exportTemplates)
|
||||
{
|
||||
$this->database = $database;
|
||||
$this->exportTemplates = $exportTemplates;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\ConfigStorage\Features;
|
||||
|
||||
use PhpMyAdmin\Dbal\DatabaseName;
|
||||
use PhpMyAdmin\Dbal\TableName;
|
||||
|
||||
/**
|
||||
* @psalm-immutable
|
||||
*/
|
||||
final class FavoriteTablesFeature
|
||||
{
|
||||
/** @var DatabaseName */
|
||||
public $database;
|
||||
|
||||
/** @var TableName */
|
||||
public $favorite;
|
||||
|
||||
public function __construct(DatabaseName $database, TableName $favorite)
|
||||
{
|
||||
$this->database = $database;
|
||||
$this->favorite = $favorite;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\ConfigStorage\Features;
|
||||
|
||||
use PhpMyAdmin\Dbal\DatabaseName;
|
||||
use PhpMyAdmin\Dbal\TableName;
|
||||
|
||||
/**
|
||||
* @psalm-immutable
|
||||
*/
|
||||
final class NavigationItemsHidingFeature
|
||||
{
|
||||
/** @var DatabaseName */
|
||||
public $database;
|
||||
|
||||
/** @var TableName */
|
||||
public $navigationHiding;
|
||||
|
||||
public function __construct(DatabaseName $database, TableName $navigationHiding)
|
||||
{
|
||||
$this->database = $database;
|
||||
$this->navigationHiding = $navigationHiding;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\ConfigStorage\Features;
|
||||
|
||||
use PhpMyAdmin\Dbal\DatabaseName;
|
||||
use PhpMyAdmin\Dbal\TableName;
|
||||
|
||||
/**
|
||||
* @psalm-immutable
|
||||
*/
|
||||
final class PdfFeature
|
||||
{
|
||||
/** @var DatabaseName */
|
||||
public $database;
|
||||
|
||||
/** @var TableName */
|
||||
public $pdfPages;
|
||||
|
||||
/** @var TableName */
|
||||
public $tableCoords;
|
||||
|
||||
public function __construct(DatabaseName $database, TableName $pdfPages, TableName $tableCoords)
|
||||
{
|
||||
$this->database = $database;
|
||||
$this->pdfPages = $pdfPages;
|
||||
$this->tableCoords = $tableCoords;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\ConfigStorage\Features;
|
||||
|
||||
use PhpMyAdmin\Dbal\DatabaseName;
|
||||
use PhpMyAdmin\Dbal\TableName;
|
||||
|
||||
/**
|
||||
* @psalm-immutable
|
||||
*/
|
||||
final class RecentlyUsedTablesFeature
|
||||
{
|
||||
/** @var DatabaseName */
|
||||
public $database;
|
||||
|
||||
/** @var TableName */
|
||||
public $recent;
|
||||
|
||||
public function __construct(DatabaseName $database, TableName $recent)
|
||||
{
|
||||
$this->database = $database;
|
||||
$this->recent = $recent;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\ConfigStorage\Features;
|
||||
|
||||
use PhpMyAdmin\Dbal\DatabaseName;
|
||||
use PhpMyAdmin\Dbal\TableName;
|
||||
|
||||
/**
|
||||
* @psalm-immutable
|
||||
*/
|
||||
final class RelationFeature
|
||||
{
|
||||
/** @var DatabaseName */
|
||||
public $database;
|
||||
|
||||
/** @var TableName */
|
||||
public $relation;
|
||||
|
||||
public function __construct(DatabaseName $database, TableName $relation)
|
||||
{
|
||||
$this->database = $database;
|
||||
$this->relation = $relation;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\ConfigStorage\Features;
|
||||
|
||||
use PhpMyAdmin\Dbal\DatabaseName;
|
||||
use PhpMyAdmin\Dbal\TableName;
|
||||
|
||||
/**
|
||||
* @psalm-immutable
|
||||
*/
|
||||
final class SavedQueryByExampleSearchesFeature
|
||||
{
|
||||
/** @var DatabaseName */
|
||||
public $database;
|
||||
|
||||
/** @var TableName */
|
||||
public $savedSearches;
|
||||
|
||||
public function __construct(DatabaseName $database, TableName $savedSearches)
|
||||
{
|
||||
$this->database = $database;
|
||||
$this->savedSearches = $savedSearches;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\ConfigStorage\Features;
|
||||
|
||||
use PhpMyAdmin\Dbal\DatabaseName;
|
||||
use PhpMyAdmin\Dbal\TableName;
|
||||
|
||||
/**
|
||||
* @psalm-immutable
|
||||
*/
|
||||
final class SqlHistoryFeature
|
||||
{
|
||||
/** @var DatabaseName */
|
||||
public $database;
|
||||
|
||||
/** @var TableName */
|
||||
public $history;
|
||||
|
||||
public function __construct(DatabaseName $database, TableName $history)
|
||||
{
|
||||
$this->database = $database;
|
||||
$this->history = $history;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\ConfigStorage\Features;
|
||||
|
||||
use PhpMyAdmin\Dbal\DatabaseName;
|
||||
use PhpMyAdmin\Dbal\TableName;
|
||||
|
||||
/**
|
||||
* @psalm-immutable
|
||||
*/
|
||||
final class TrackingFeature
|
||||
{
|
||||
/** @var DatabaseName */
|
||||
public $database;
|
||||
|
||||
/** @var TableName */
|
||||
public $tracking;
|
||||
|
||||
public function __construct(DatabaseName $database, TableName $tracking)
|
||||
{
|
||||
$this->database = $database;
|
||||
$this->tracking = $tracking;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\ConfigStorage\Features;
|
||||
|
||||
use PhpMyAdmin\Dbal\DatabaseName;
|
||||
use PhpMyAdmin\Dbal\TableName;
|
||||
|
||||
/**
|
||||
* @psalm-immutable
|
||||
*/
|
||||
final class UiPreferencesFeature
|
||||
{
|
||||
/** @var DatabaseName */
|
||||
public $database;
|
||||
|
||||
/** @var TableName */
|
||||
public $tableUiPrefs;
|
||||
|
||||
public function __construct(DatabaseName $database, TableName $tableUiPrefs)
|
||||
{
|
||||
$this->database = $database;
|
||||
$this->tableUiPrefs = $tableUiPrefs;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\ConfigStorage\Features;
|
||||
|
||||
use PhpMyAdmin\Dbal\DatabaseName;
|
||||
use PhpMyAdmin\Dbal\TableName;
|
||||
|
||||
/**
|
||||
* @psalm-immutable
|
||||
*/
|
||||
final class UserPreferencesFeature
|
||||
{
|
||||
/** @var DatabaseName */
|
||||
public $database;
|
||||
|
||||
/** @var TableName */
|
||||
public $userConfig;
|
||||
|
||||
public function __construct(DatabaseName $database, TableName $userConfig)
|
||||
{
|
||||
$this->database = $database;
|
||||
$this->userConfig = $userConfig;
|
||||
}
|
||||
}
|
1814
admin/phpMyAdmin/libraries/classes/ConfigStorage/Relation.php
Normal file
1814
admin/phpMyAdmin/libraries/classes/ConfigStorage/Relation.php
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,384 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\ConfigStorage;
|
||||
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Util;
|
||||
|
||||
/**
|
||||
* Set of functions used for cleaning up phpMyAdmin tables
|
||||
*/
|
||||
class RelationCleanup
|
||||
{
|
||||
/** @var Relation */
|
||||
public $relation;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
public $dbi;
|
||||
|
||||
/**
|
||||
* @param DatabaseInterface $dbi DatabaseInterface object
|
||||
* @param Relation $relation Relation object
|
||||
*/
|
||||
public function __construct($dbi, Relation $relation)
|
||||
{
|
||||
$this->dbi = $dbi;
|
||||
$this->relation = $relation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup column related relation stuff
|
||||
*
|
||||
* @param string $db database name
|
||||
* @param string $table table name
|
||||
* @param string $column column name
|
||||
*/
|
||||
public function column($db, $table, $column): void
|
||||
{
|
||||
$relationParameters = $this->relation->getRelationParameters();
|
||||
|
||||
if ($relationParameters->columnCommentsFeature !== null) {
|
||||
$remove_query = 'DELETE FROM '
|
||||
. Util::backquote($relationParameters->columnCommentsFeature->database)
|
||||
. '.' . Util::backquote($relationParameters->columnCommentsFeature->columnInfo)
|
||||
. ' WHERE db_name = \'' . $this->dbi->escapeString($db) . '\''
|
||||
. ' AND table_name = \'' . $this->dbi->escapeString($table)
|
||||
. '\''
|
||||
. ' AND column_name = \'' . $this->dbi->escapeString($column)
|
||||
. '\'';
|
||||
$this->dbi->queryAsControlUser($remove_query);
|
||||
}
|
||||
|
||||
if ($relationParameters->displayFeature !== null) {
|
||||
$remove_query = 'DELETE FROM '
|
||||
. Util::backquote($relationParameters->displayFeature->database)
|
||||
. '.' . Util::backquote($relationParameters->displayFeature->tableInfo)
|
||||
. ' WHERE db_name = \'' . $this->dbi->escapeString($db) . '\''
|
||||
. ' AND table_name = \'' . $this->dbi->escapeString($table)
|
||||
. '\''
|
||||
. ' AND display_field = \'' . $this->dbi->escapeString($column)
|
||||
. '\'';
|
||||
$this->dbi->queryAsControlUser($remove_query);
|
||||
}
|
||||
|
||||
if ($relationParameters->relationFeature === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$remove_query = 'DELETE FROM '
|
||||
. Util::backquote($relationParameters->relationFeature->database)
|
||||
. '.' . Util::backquote($relationParameters->relationFeature->relation)
|
||||
. ' WHERE master_db = \'' . $this->dbi->escapeString($db)
|
||||
. '\''
|
||||
. ' AND master_table = \'' . $this->dbi->escapeString($table)
|
||||
. '\''
|
||||
. ' AND master_field = \'' . $this->dbi->escapeString($column)
|
||||
. '\'';
|
||||
$this->dbi->queryAsControlUser($remove_query);
|
||||
|
||||
$remove_query = 'DELETE FROM '
|
||||
. Util::backquote($relationParameters->relationFeature->database)
|
||||
. '.' . Util::backquote($relationParameters->relationFeature->relation)
|
||||
. ' WHERE foreign_db = \'' . $this->dbi->escapeString($db)
|
||||
. '\''
|
||||
. ' AND foreign_table = \'' . $this->dbi->escapeString($table)
|
||||
. '\''
|
||||
. ' AND foreign_field = \'' . $this->dbi->escapeString($column)
|
||||
. '\'';
|
||||
$this->dbi->queryAsControlUser($remove_query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup table related relation stuff
|
||||
*
|
||||
* @param string $db database name
|
||||
* @param string $table table name
|
||||
*/
|
||||
public function table($db, $table): void
|
||||
{
|
||||
$relationParameters = $this->relation->getRelationParameters();
|
||||
|
||||
if ($relationParameters->columnCommentsFeature !== null) {
|
||||
$remove_query = 'DELETE FROM '
|
||||
. Util::backquote($relationParameters->columnCommentsFeature->database)
|
||||
. '.' . Util::backquote($relationParameters->columnCommentsFeature->columnInfo)
|
||||
. ' WHERE db_name = \'' . $this->dbi->escapeString($db) . '\''
|
||||
. ' AND table_name = \'' . $this->dbi->escapeString($table)
|
||||
. '\'';
|
||||
$this->dbi->queryAsControlUser($remove_query);
|
||||
}
|
||||
|
||||
if ($relationParameters->displayFeature !== null) {
|
||||
$remove_query = 'DELETE FROM '
|
||||
. Util::backquote($relationParameters->displayFeature->database)
|
||||
. '.' . Util::backquote($relationParameters->displayFeature->tableInfo)
|
||||
. ' WHERE db_name = \'' . $this->dbi->escapeString($db) . '\''
|
||||
. ' AND table_name = \'' . $this->dbi->escapeString($table)
|
||||
. '\'';
|
||||
$this->dbi->queryAsControlUser($remove_query);
|
||||
}
|
||||
|
||||
if ($relationParameters->pdfFeature !== null) {
|
||||
$remove_query = 'DELETE FROM '
|
||||
. Util::backquote($relationParameters->pdfFeature->database)
|
||||
. '.' . Util::backquote($relationParameters->pdfFeature->tableCoords)
|
||||
. ' WHERE db_name = \'' . $this->dbi->escapeString($db) . '\''
|
||||
. ' AND table_name = \'' . $this->dbi->escapeString($table)
|
||||
. '\'';
|
||||
$this->dbi->queryAsControlUser($remove_query);
|
||||
}
|
||||
|
||||
if ($relationParameters->relationFeature !== null) {
|
||||
$remove_query = 'DELETE FROM '
|
||||
. Util::backquote($relationParameters->relationFeature->database)
|
||||
. '.' . Util::backquote($relationParameters->relationFeature->relation)
|
||||
. ' WHERE master_db = \'' . $this->dbi->escapeString($db)
|
||||
. '\''
|
||||
. ' AND master_table = \'' . $this->dbi->escapeString($table)
|
||||
. '\'';
|
||||
$this->dbi->queryAsControlUser($remove_query);
|
||||
|
||||
$remove_query = 'DELETE FROM '
|
||||
. Util::backquote($relationParameters->relationFeature->database)
|
||||
. '.' . Util::backquote($relationParameters->relationFeature->relation)
|
||||
. ' WHERE foreign_db = \'' . $this->dbi->escapeString($db)
|
||||
. '\''
|
||||
. ' AND foreign_table = \'' . $this->dbi->escapeString($table)
|
||||
. '\'';
|
||||
$this->dbi->queryAsControlUser($remove_query);
|
||||
}
|
||||
|
||||
if ($relationParameters->uiPreferencesFeature !== null) {
|
||||
$remove_query = 'DELETE FROM '
|
||||
. Util::backquote($relationParameters->uiPreferencesFeature->database)
|
||||
. '.' . Util::backquote($relationParameters->uiPreferencesFeature->tableUiPrefs)
|
||||
. ' WHERE db_name = \'' . $this->dbi->escapeString($db) . '\''
|
||||
. ' AND table_name = \'' . $this->dbi->escapeString($table)
|
||||
. '\'';
|
||||
$this->dbi->queryAsControlUser($remove_query);
|
||||
}
|
||||
|
||||
if ($relationParameters->navigationItemsHidingFeature === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$remove_query = 'DELETE FROM '
|
||||
. Util::backquote($relationParameters->navigationItemsHidingFeature->database)
|
||||
. '.' . Util::backquote($relationParameters->navigationItemsHidingFeature->navigationHiding)
|
||||
. ' WHERE db_name = \'' . $this->dbi->escapeString($db) . '\''
|
||||
. ' AND (table_name = \'' . $this->dbi->escapeString($table)
|
||||
. '\''
|
||||
. ' OR (item_name = \'' . $this->dbi->escapeString($table)
|
||||
. '\''
|
||||
. ' AND item_type = \'table\'))';
|
||||
$this->dbi->queryAsControlUser($remove_query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup database related relation stuff
|
||||
*
|
||||
* @param string $db database name
|
||||
*/
|
||||
public function database($db): void
|
||||
{
|
||||
$relationParameters = $this->relation->getRelationParameters();
|
||||
if ($relationParameters->db === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($relationParameters->columnCommentsFeature !== null) {
|
||||
$remove_query = 'DELETE FROM '
|
||||
. Util::backquote($relationParameters->columnCommentsFeature->database)
|
||||
. '.' . Util::backquote($relationParameters->columnCommentsFeature->columnInfo)
|
||||
. ' WHERE db_name = \'' . $this->dbi->escapeString($db) . '\'';
|
||||
$this->dbi->queryAsControlUser($remove_query);
|
||||
}
|
||||
|
||||
if ($relationParameters->bookmarkFeature !== null) {
|
||||
$remove_query = 'DELETE FROM '
|
||||
. Util::backquote($relationParameters->bookmarkFeature->database)
|
||||
. '.' . Util::backquote($relationParameters->bookmarkFeature->bookmark)
|
||||
. ' WHERE dbase = \'' . $this->dbi->escapeString($db) . '\'';
|
||||
$this->dbi->queryAsControlUser($remove_query);
|
||||
}
|
||||
|
||||
if ($relationParameters->displayFeature !== null) {
|
||||
$remove_query = 'DELETE FROM '
|
||||
. Util::backquote($relationParameters->displayFeature->database)
|
||||
. '.' . Util::backquote($relationParameters->displayFeature->tableInfo)
|
||||
. ' WHERE db_name = \'' . $this->dbi->escapeString($db) . '\'';
|
||||
$this->dbi->queryAsControlUser($remove_query);
|
||||
}
|
||||
|
||||
if ($relationParameters->pdfFeature !== null) {
|
||||
$remove_query = 'DELETE FROM '
|
||||
. Util::backquote($relationParameters->pdfFeature->database)
|
||||
. '.' . Util::backquote($relationParameters->pdfFeature->pdfPages)
|
||||
. ' WHERE db_name = \'' . $this->dbi->escapeString($db) . '\'';
|
||||
$this->dbi->queryAsControlUser($remove_query);
|
||||
|
||||
$remove_query = 'DELETE FROM '
|
||||
. Util::backquote($relationParameters->pdfFeature->database)
|
||||
. '.' . Util::backquote($relationParameters->pdfFeature->tableCoords)
|
||||
. ' WHERE db_name = \'' . $this->dbi->escapeString($db) . '\'';
|
||||
$this->dbi->queryAsControlUser($remove_query);
|
||||
}
|
||||
|
||||
if ($relationParameters->relationFeature !== null) {
|
||||
$remove_query = 'DELETE FROM '
|
||||
. Util::backquote($relationParameters->relationFeature->database)
|
||||
. '.' . Util::backquote($relationParameters->relationFeature->relation)
|
||||
. ' WHERE master_db = \''
|
||||
. $this->dbi->escapeString($db) . '\'';
|
||||
$this->dbi->queryAsControlUser($remove_query);
|
||||
|
||||
$remove_query = 'DELETE FROM '
|
||||
. Util::backquote($relationParameters->relationFeature->database)
|
||||
. '.' . Util::backquote($relationParameters->relationFeature->relation)
|
||||
. ' WHERE foreign_db = \'' . $this->dbi->escapeString($db)
|
||||
. '\'';
|
||||
$this->dbi->queryAsControlUser($remove_query);
|
||||
}
|
||||
|
||||
if ($relationParameters->uiPreferencesFeature !== null) {
|
||||
$remove_query = 'DELETE FROM '
|
||||
. Util::backquote($relationParameters->uiPreferencesFeature->database)
|
||||
. '.' . Util::backquote($relationParameters->uiPreferencesFeature->tableUiPrefs)
|
||||
. ' WHERE db_name = \'' . $this->dbi->escapeString($db) . '\'';
|
||||
$this->dbi->queryAsControlUser($remove_query);
|
||||
}
|
||||
|
||||
if ($relationParameters->navigationItemsHidingFeature !== null) {
|
||||
$remove_query = 'DELETE FROM '
|
||||
. Util::backquote($relationParameters->navigationItemsHidingFeature->database)
|
||||
. '.' . Util::backquote($relationParameters->navigationItemsHidingFeature->navigationHiding)
|
||||
. ' WHERE db_name = \'' . $this->dbi->escapeString($db) . '\'';
|
||||
$this->dbi->queryAsControlUser($remove_query);
|
||||
}
|
||||
|
||||
if ($relationParameters->savedQueryByExampleSearchesFeature !== null) {
|
||||
$remove_query = 'DELETE FROM '
|
||||
. Util::backquote($relationParameters->savedQueryByExampleSearchesFeature->database)
|
||||
. '.' . Util::backquote($relationParameters->savedQueryByExampleSearchesFeature->savedSearches)
|
||||
. ' WHERE db_name = \'' . $this->dbi->escapeString($db) . '\'';
|
||||
$this->dbi->queryAsControlUser($remove_query);
|
||||
}
|
||||
|
||||
if ($relationParameters->centralColumnsFeature === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$remove_query = 'DELETE FROM '
|
||||
. Util::backquote($relationParameters->centralColumnsFeature->database)
|
||||
. '.' . Util::backquote($relationParameters->centralColumnsFeature->centralColumns)
|
||||
. ' WHERE db_name = \'' . $this->dbi->escapeString($db) . '\'';
|
||||
$this->dbi->queryAsControlUser($remove_query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup user related relation stuff
|
||||
*
|
||||
* @param string $username username
|
||||
*/
|
||||
public function user($username): void
|
||||
{
|
||||
$relationParameters = $this->relation->getRelationParameters();
|
||||
if ($relationParameters->db === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($relationParameters->bookmarkFeature !== null) {
|
||||
$remove_query = 'DELETE FROM '
|
||||
. Util::backquote($relationParameters->bookmarkFeature->database)
|
||||
. '.' . Util::backquote($relationParameters->bookmarkFeature->bookmark)
|
||||
. " WHERE `user` = '" . $this->dbi->escapeString($username)
|
||||
. "'";
|
||||
$this->dbi->queryAsControlUser($remove_query);
|
||||
}
|
||||
|
||||
if ($relationParameters->sqlHistoryFeature !== null) {
|
||||
$remove_query = 'DELETE FROM '
|
||||
. Util::backquote($relationParameters->sqlHistoryFeature->database)
|
||||
. '.' . Util::backquote($relationParameters->sqlHistoryFeature->history)
|
||||
. " WHERE `username` = '" . $this->dbi->escapeString($username)
|
||||
. "'";
|
||||
$this->dbi->queryAsControlUser($remove_query);
|
||||
}
|
||||
|
||||
if ($relationParameters->recentlyUsedTablesFeature !== null) {
|
||||
$remove_query = 'DELETE FROM '
|
||||
. Util::backquote($relationParameters->recentlyUsedTablesFeature->database)
|
||||
. '.' . Util::backquote($relationParameters->recentlyUsedTablesFeature->recent)
|
||||
. " WHERE `username` = '" . $this->dbi->escapeString($username)
|
||||
. "'";
|
||||
$this->dbi->queryAsControlUser($remove_query);
|
||||
}
|
||||
|
||||
if ($relationParameters->favoriteTablesFeature !== null) {
|
||||
$remove_query = 'DELETE FROM '
|
||||
. Util::backquote($relationParameters->favoriteTablesFeature->database)
|
||||
. '.' . Util::backquote($relationParameters->favoriteTablesFeature->favorite)
|
||||
. " WHERE `username` = '" . $this->dbi->escapeString($username)
|
||||
. "'";
|
||||
$this->dbi->queryAsControlUser($remove_query);
|
||||
}
|
||||
|
||||
if ($relationParameters->uiPreferencesFeature !== null) {
|
||||
$remove_query = 'DELETE FROM '
|
||||
. Util::backquote($relationParameters->uiPreferencesFeature->database)
|
||||
. '.' . Util::backquote($relationParameters->uiPreferencesFeature->tableUiPrefs)
|
||||
. " WHERE `username` = '" . $this->dbi->escapeString($username)
|
||||
. "'";
|
||||
$this->dbi->queryAsControlUser($remove_query);
|
||||
}
|
||||
|
||||
if ($relationParameters->userPreferencesFeature !== null) {
|
||||
$remove_query = 'DELETE FROM '
|
||||
. Util::backquote($relationParameters->userPreferencesFeature->database)
|
||||
. '.' . Util::backquote($relationParameters->userPreferencesFeature->userConfig)
|
||||
. " WHERE `username` = '" . $this->dbi->escapeString($username)
|
||||
. "'";
|
||||
$this->dbi->queryAsControlUser($remove_query);
|
||||
}
|
||||
|
||||
if ($relationParameters->configurableMenusFeature !== null) {
|
||||
$remove_query = 'DELETE FROM '
|
||||
. Util::backquote($relationParameters->configurableMenusFeature->database)
|
||||
. '.' . Util::backquote($relationParameters->configurableMenusFeature->users)
|
||||
. " WHERE `username` = '" . $this->dbi->escapeString($username)
|
||||
. "'";
|
||||
$this->dbi->queryAsControlUser($remove_query);
|
||||
}
|
||||
|
||||
if ($relationParameters->navigationItemsHidingFeature !== null) {
|
||||
$remove_query = 'DELETE FROM '
|
||||
. Util::backquote($relationParameters->navigationItemsHidingFeature->database)
|
||||
. '.' . Util::backquote($relationParameters->navigationItemsHidingFeature->navigationHiding)
|
||||
. " WHERE `username` = '" . $this->dbi->escapeString($username)
|
||||
. "'";
|
||||
$this->dbi->queryAsControlUser($remove_query);
|
||||
}
|
||||
|
||||
if ($relationParameters->savedQueryByExampleSearchesFeature !== null) {
|
||||
$remove_query = 'DELETE FROM '
|
||||
. Util::backquote($relationParameters->savedQueryByExampleSearchesFeature->database)
|
||||
. '.' . Util::backquote($relationParameters->savedQueryByExampleSearchesFeature->savedSearches)
|
||||
. " WHERE `username` = '" . $this->dbi->escapeString($username)
|
||||
. "'";
|
||||
$this->dbi->queryAsControlUser($remove_query);
|
||||
}
|
||||
|
||||
if ($relationParameters->databaseDesignerSettingsFeature === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$remove_query = 'DELETE FROM '
|
||||
. Util::backquote($relationParameters->databaseDesignerSettingsFeature->database)
|
||||
. '.' . Util::backquote($relationParameters->databaseDesignerSettingsFeature->designerSettings)
|
||||
. " WHERE `username` = '" . $this->dbi->escapeString($username)
|
||||
. "'";
|
||||
$this->dbi->queryAsControlUser($remove_query);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,472 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\ConfigStorage;
|
||||
|
||||
use PhpMyAdmin\ConfigStorage\Features\BookmarkFeature;
|
||||
use PhpMyAdmin\ConfigStorage\Features\BrowserTransformationFeature;
|
||||
use PhpMyAdmin\ConfigStorage\Features\CentralColumnsFeature;
|
||||
use PhpMyAdmin\ConfigStorage\Features\ColumnCommentsFeature;
|
||||
use PhpMyAdmin\ConfigStorage\Features\ConfigurableMenusFeature;
|
||||
use PhpMyAdmin\ConfigStorage\Features\DatabaseDesignerSettingsFeature;
|
||||
use PhpMyAdmin\ConfigStorage\Features\DisplayFeature;
|
||||
use PhpMyAdmin\ConfigStorage\Features\ExportTemplatesFeature;
|
||||
use PhpMyAdmin\ConfigStorage\Features\FavoriteTablesFeature;
|
||||
use PhpMyAdmin\ConfigStorage\Features\NavigationItemsHidingFeature;
|
||||
use PhpMyAdmin\ConfigStorage\Features\PdfFeature;
|
||||
use PhpMyAdmin\ConfigStorage\Features\RecentlyUsedTablesFeature;
|
||||
use PhpMyAdmin\ConfigStorage\Features\RelationFeature;
|
||||
use PhpMyAdmin\ConfigStorage\Features\SavedQueryByExampleSearchesFeature;
|
||||
use PhpMyAdmin\ConfigStorage\Features\SqlHistoryFeature;
|
||||
use PhpMyAdmin\ConfigStorage\Features\TrackingFeature;
|
||||
use PhpMyAdmin\ConfigStorage\Features\UiPreferencesFeature;
|
||||
use PhpMyAdmin\ConfigStorage\Features\UserPreferencesFeature;
|
||||
use PhpMyAdmin\Dbal\DatabaseName;
|
||||
use PhpMyAdmin\Dbal\TableName;
|
||||
use PhpMyAdmin\Version;
|
||||
use Webmozart\Assert\Assert;
|
||||
use Webmozart\Assert\InvalidArgumentException;
|
||||
|
||||
use function is_string;
|
||||
|
||||
/**
|
||||
* @psalm-immutable
|
||||
*/
|
||||
final class RelationParameters
|
||||
{
|
||||
/**
|
||||
* @var string|null
|
||||
* @psalm-var non-empty-string|null
|
||||
*/
|
||||
public $user;
|
||||
/** @var DatabaseName|null */
|
||||
public $db;
|
||||
|
||||
/** @var BookmarkFeature|null */
|
||||
public $bookmarkFeature;
|
||||
/** @var BrowserTransformationFeature|null */
|
||||
public $browserTransformationFeature;
|
||||
/** @var CentralColumnsFeature|null */
|
||||
public $centralColumnsFeature;
|
||||
/** @var ColumnCommentsFeature|null */
|
||||
public $columnCommentsFeature;
|
||||
/** @var ConfigurableMenusFeature|null */
|
||||
public $configurableMenusFeature;
|
||||
/** @var DatabaseDesignerSettingsFeature|null */
|
||||
public $databaseDesignerSettingsFeature;
|
||||
/** @var DisplayFeature|null */
|
||||
public $displayFeature;
|
||||
/** @var ExportTemplatesFeature|null */
|
||||
public $exportTemplatesFeature;
|
||||
/** @var FavoriteTablesFeature|null */
|
||||
public $favoriteTablesFeature;
|
||||
/** @var NavigationItemsHidingFeature|null */
|
||||
public $navigationItemsHidingFeature;
|
||||
/** @var PdfFeature|null */
|
||||
public $pdfFeature;
|
||||
/** @var RecentlyUsedTablesFeature|null */
|
||||
public $recentlyUsedTablesFeature;
|
||||
/** @var RelationFeature|null */
|
||||
public $relationFeature;
|
||||
/** @var SavedQueryByExampleSearchesFeature|null */
|
||||
public $savedQueryByExampleSearchesFeature;
|
||||
/** @var SqlHistoryFeature|null */
|
||||
public $sqlHistoryFeature;
|
||||
/** @var TrackingFeature|null */
|
||||
public $trackingFeature;
|
||||
/** @var UiPreferencesFeature|null */
|
||||
public $uiPreferencesFeature;
|
||||
/** @var UserPreferencesFeature|null */
|
||||
public $userPreferencesFeature;
|
||||
|
||||
/**
|
||||
* @psalm-param non-empty-string|null $user
|
||||
*/
|
||||
public function __construct(
|
||||
?string $user,
|
||||
?DatabaseName $db,
|
||||
?BookmarkFeature $bookmarkFeature = null,
|
||||
?BrowserTransformationFeature $browserTransformationFeature = null,
|
||||
?CentralColumnsFeature $centralColumnsFeature = null,
|
||||
?ColumnCommentsFeature $columnCommentsFeature = null,
|
||||
?ConfigurableMenusFeature $configurableMenusFeature = null,
|
||||
?DatabaseDesignerSettingsFeature $databaseDesignerSettingsFeature = null,
|
||||
?DisplayFeature $displayFeature = null,
|
||||
?ExportTemplatesFeature $exportTemplatesFeature = null,
|
||||
?FavoriteTablesFeature $favoriteTablesFeature = null,
|
||||
?NavigationItemsHidingFeature $navigationItemsHidingFeature = null,
|
||||
?PdfFeature $pdfFeature = null,
|
||||
?RecentlyUsedTablesFeature $recentlyUsedTablesFeature = null,
|
||||
?RelationFeature $relationFeature = null,
|
||||
?SavedQueryByExampleSearchesFeature $savedQueryByExampleSearchesFeature = null,
|
||||
?SqlHistoryFeature $sqlHistoryFeature = null,
|
||||
?TrackingFeature $trackingFeature = null,
|
||||
?UiPreferencesFeature $uiPreferencesFeature = null,
|
||||
?UserPreferencesFeature $userPreferencesFeature = null
|
||||
) {
|
||||
$this->user = $user;
|
||||
$this->db = $db;
|
||||
$this->bookmarkFeature = $bookmarkFeature;
|
||||
$this->browserTransformationFeature = $browserTransformationFeature;
|
||||
$this->centralColumnsFeature = $centralColumnsFeature;
|
||||
$this->columnCommentsFeature = $columnCommentsFeature;
|
||||
$this->configurableMenusFeature = $configurableMenusFeature;
|
||||
$this->databaseDesignerSettingsFeature = $databaseDesignerSettingsFeature;
|
||||
$this->displayFeature = $displayFeature;
|
||||
$this->exportTemplatesFeature = $exportTemplatesFeature;
|
||||
$this->favoriteTablesFeature = $favoriteTablesFeature;
|
||||
$this->navigationItemsHidingFeature = $navigationItemsHidingFeature;
|
||||
$this->pdfFeature = $pdfFeature;
|
||||
$this->recentlyUsedTablesFeature = $recentlyUsedTablesFeature;
|
||||
$this->relationFeature = $relationFeature;
|
||||
$this->savedQueryByExampleSearchesFeature = $savedQueryByExampleSearchesFeature;
|
||||
$this->sqlHistoryFeature = $sqlHistoryFeature;
|
||||
$this->trackingFeature = $trackingFeature;
|
||||
$this->uiPreferencesFeature = $uiPreferencesFeature;
|
||||
$this->userPreferencesFeature = $userPreferencesFeature;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $params
|
||||
*/
|
||||
public static function fromArray(array $params): self
|
||||
{
|
||||
$user = null;
|
||||
if (isset($params['user']) && is_string($params['user']) && $params['user'] !== '') {
|
||||
$user = $params['user'];
|
||||
}
|
||||
|
||||
try {
|
||||
Assert::keyExists($params, 'db');
|
||||
$db = DatabaseName::fromValue($params['db']);
|
||||
} catch (InvalidArgumentException $exception) {
|
||||
return new self($user, null);
|
||||
}
|
||||
|
||||
$bookmarkFeature = null;
|
||||
if (isset($params['bookmarkwork'], $params['bookmark']) && $params['bookmarkwork']) {
|
||||
$bookmark = self::getTableName($params['bookmark']);
|
||||
if ($bookmark !== null) {
|
||||
$bookmarkFeature = new BookmarkFeature($db, $bookmark);
|
||||
}
|
||||
}
|
||||
|
||||
$columnInfo = self::getTableName($params['column_info'] ?? null);
|
||||
$browserTransformationFeature = null;
|
||||
if (isset($params['mimework']) && $params['mimework'] && $columnInfo !== null) {
|
||||
$browserTransformationFeature = new BrowserTransformationFeature($db, $columnInfo);
|
||||
}
|
||||
|
||||
$columnCommentsFeature = null;
|
||||
if (isset($params['commwork']) && $params['commwork'] && $columnInfo !== null) {
|
||||
$columnCommentsFeature = new ColumnCommentsFeature($db, $columnInfo);
|
||||
}
|
||||
|
||||
$centralColumnsFeature = null;
|
||||
if (isset($params['centralcolumnswork'], $params['central_columns']) && $params['centralcolumnswork']) {
|
||||
$centralColumns = self::getTableName($params['central_columns']);
|
||||
if ($centralColumns !== null) {
|
||||
$centralColumnsFeature = new CentralColumnsFeature($db, $centralColumns);
|
||||
}
|
||||
}
|
||||
|
||||
$configurableMenusFeature = null;
|
||||
if (isset($params['menuswork'], $params['usergroups'], $params['users']) && $params['menuswork']) {
|
||||
$userGroups = self::getTableName($params['usergroups']);
|
||||
$users = self::getTableName($params['users']);
|
||||
if ($userGroups !== null && $users !== null) {
|
||||
$configurableMenusFeature = new ConfigurableMenusFeature($db, $userGroups, $users);
|
||||
}
|
||||
}
|
||||
|
||||
$databaseDesignerSettingsFeature = null;
|
||||
if (isset($params['designersettingswork'], $params['designer_settings']) && $params['designersettingswork']) {
|
||||
$designerSettings = self::getTableName($params['designer_settings']);
|
||||
if ($designerSettings !== null) {
|
||||
$databaseDesignerSettingsFeature = new DatabaseDesignerSettingsFeature($db, $designerSettings);
|
||||
}
|
||||
}
|
||||
|
||||
$relation = self::getTableName($params['relation'] ?? null);
|
||||
$displayFeature = null;
|
||||
if (isset($params['displaywork'], $params['table_info']) && $params['displaywork'] && $relation !== null) {
|
||||
$tableInfo = self::getTableName($params['table_info']);
|
||||
if ($tableInfo !== null) {
|
||||
$displayFeature = new DisplayFeature($db, $relation, $tableInfo);
|
||||
}
|
||||
}
|
||||
|
||||
$exportTemplatesFeature = null;
|
||||
if (isset($params['exporttemplateswork'], $params['export_templates']) && $params['exporttemplateswork']) {
|
||||
$exportTemplates = self::getTableName($params['export_templates']);
|
||||
if ($exportTemplates !== null) {
|
||||
$exportTemplatesFeature = new ExportTemplatesFeature($db, $exportTemplates);
|
||||
}
|
||||
}
|
||||
|
||||
$favoriteTablesFeature = null;
|
||||
if (isset($params['favoritework'], $params['favorite']) && $params['favoritework']) {
|
||||
$favorite = self::getTableName($params['favorite']);
|
||||
if ($favorite !== null) {
|
||||
$favoriteTablesFeature = new FavoriteTablesFeature($db, $favorite);
|
||||
}
|
||||
}
|
||||
|
||||
$navigationItemsHidingFeature = null;
|
||||
if (isset($params['navwork'], $params['navigationhiding']) && $params['navwork']) {
|
||||
$navigationHiding = self::getTableName($params['navigationhiding']);
|
||||
if ($navigationHiding !== null) {
|
||||
$navigationItemsHidingFeature = new NavigationItemsHidingFeature($db, $navigationHiding);
|
||||
}
|
||||
}
|
||||
|
||||
$pdfFeature = null;
|
||||
if (isset($params['pdfwork'], $params['pdf_pages'], $params['table_coords']) && $params['pdfwork']) {
|
||||
$pdfPages = self::getTableName($params['pdf_pages']);
|
||||
$tableCoords = self::getTableName($params['table_coords']);
|
||||
if ($pdfPages !== null && $tableCoords !== null) {
|
||||
$pdfFeature = new PdfFeature($db, $pdfPages, $tableCoords);
|
||||
}
|
||||
}
|
||||
|
||||
$recentlyUsedTablesFeature = null;
|
||||
if (isset($params['recentwork'], $params['recent']) && $params['recentwork']) {
|
||||
$recent = self::getTableName($params['recent']);
|
||||
if ($recent !== null) {
|
||||
$recentlyUsedTablesFeature = new RecentlyUsedTablesFeature($db, $recent);
|
||||
}
|
||||
}
|
||||
|
||||
$relationFeature = null;
|
||||
if (isset($params['relwork']) && $params['relwork'] && $relation !== null) {
|
||||
$relationFeature = new RelationFeature($db, $relation);
|
||||
}
|
||||
|
||||
$savedQueryByExampleSearchesFeature = null;
|
||||
if (isset($params['savedsearcheswork'], $params['savedsearches']) && $params['savedsearcheswork']) {
|
||||
$savedSearches = self::getTableName($params['savedsearches']);
|
||||
if ($savedSearches !== null) {
|
||||
$savedQueryByExampleSearchesFeature = new SavedQueryByExampleSearchesFeature($db, $savedSearches);
|
||||
}
|
||||
}
|
||||
|
||||
$sqlHistoryFeature = null;
|
||||
if (isset($params['historywork'], $params['history']) && $params['historywork']) {
|
||||
$history = self::getTableName($params['history']);
|
||||
if ($history !== null) {
|
||||
$sqlHistoryFeature = new SqlHistoryFeature($db, $history);
|
||||
}
|
||||
}
|
||||
|
||||
$trackingFeature = null;
|
||||
if (isset($params['trackingwork'], $params['tracking']) && $params['trackingwork']) {
|
||||
$tracking = self::getTableName($params['tracking']);
|
||||
if ($tracking !== null) {
|
||||
$trackingFeature = new TrackingFeature($db, $tracking);
|
||||
}
|
||||
}
|
||||
|
||||
$uiPreferencesFeature = null;
|
||||
if (isset($params['uiprefswork'], $params['table_uiprefs']) && $params['uiprefswork']) {
|
||||
$tableUiPrefs = self::getTableName($params['table_uiprefs']);
|
||||
if ($tableUiPrefs !== null) {
|
||||
$uiPreferencesFeature = new UiPreferencesFeature($db, $tableUiPrefs);
|
||||
}
|
||||
}
|
||||
|
||||
$userPreferencesFeature = null;
|
||||
if (isset($params['userconfigwork'], $params['userconfig']) && $params['userconfigwork']) {
|
||||
$userConfig = self::getTableName($params['userconfig']);
|
||||
if ($userConfig !== null) {
|
||||
$userPreferencesFeature = new UserPreferencesFeature($db, $userConfig);
|
||||
}
|
||||
}
|
||||
|
||||
return new self(
|
||||
$user,
|
||||
$db,
|
||||
$bookmarkFeature,
|
||||
$browserTransformationFeature,
|
||||
$centralColumnsFeature,
|
||||
$columnCommentsFeature,
|
||||
$configurableMenusFeature,
|
||||
$databaseDesignerSettingsFeature,
|
||||
$displayFeature,
|
||||
$exportTemplatesFeature,
|
||||
$favoriteTablesFeature,
|
||||
$navigationItemsHidingFeature,
|
||||
$pdfFeature,
|
||||
$recentlyUsedTablesFeature,
|
||||
$relationFeature,
|
||||
$savedQueryByExampleSearchesFeature,
|
||||
$sqlHistoryFeature,
|
||||
$trackingFeature,
|
||||
$uiPreferencesFeature,
|
||||
$userPreferencesFeature
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, bool|string|null>
|
||||
* @psalm-return array{
|
||||
* version: string,
|
||||
* user: (string|null),
|
||||
* db: (string|null),
|
||||
* bookmark: (string|null),
|
||||
* central_columns: (string|null),
|
||||
* column_info: (string|null),
|
||||
* designer_settings: (string|null),
|
||||
* export_templates: (string|null),
|
||||
* favorite: (string|null),
|
||||
* history: (string|null),
|
||||
* navigationhiding: (string|null),
|
||||
* pdf_pages: (string|null),
|
||||
* recent: (string|null),
|
||||
* relation: (string|null),
|
||||
* savedsearches: (string|null),
|
||||
* table_coords: (string|null),
|
||||
* table_info: (string|null),
|
||||
* table_uiprefs: (string|null),
|
||||
* tracking: (string|null),
|
||||
* userconfig: (string|null),
|
||||
* usergroups: (string|null),
|
||||
* users: (string|null),
|
||||
* bookmarkwork: bool,
|
||||
* mimework: bool,
|
||||
* centralcolumnswork: bool,
|
||||
* commwork: bool,
|
||||
* menuswork: bool,
|
||||
* designersettingswork: bool,
|
||||
* displaywork: bool,
|
||||
* exporttemplateswork: bool,
|
||||
* favoritework: bool,
|
||||
* navwork: bool,
|
||||
* pdfwork: bool,
|
||||
* recentwork: bool,
|
||||
* relwork: bool,
|
||||
* savedsearcheswork: bool,
|
||||
* historywork: bool,
|
||||
* trackingwork: bool,
|
||||
* uiprefswork: bool,
|
||||
* userconfigwork: bool,
|
||||
* allworks: bool
|
||||
* }
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
$columnInfo = null;
|
||||
if ($this->columnCommentsFeature !== null) {
|
||||
$columnInfo = $this->columnCommentsFeature->columnInfo->getName();
|
||||
} elseif ($this->browserTransformationFeature !== null) {
|
||||
$columnInfo = $this->browserTransformationFeature->columnInfo->getName();
|
||||
}
|
||||
|
||||
$relation = null;
|
||||
if ($this->relationFeature !== null) {
|
||||
$relation = $this->relationFeature->relation->getName();
|
||||
} elseif ($this->displayFeature !== null) {
|
||||
$relation = $this->displayFeature->relation->getName();
|
||||
}
|
||||
|
||||
return [
|
||||
'version' => Version::VERSION,
|
||||
'user' => $this->user,
|
||||
'db' => $this->db !== null ? $this->db->getName() : null,
|
||||
'bookmark' => $this->bookmarkFeature !== null ? $this->bookmarkFeature->bookmark->getName() : null,
|
||||
'central_columns' => $this->centralColumnsFeature !== null
|
||||
? $this->centralColumnsFeature->centralColumns->getName()
|
||||
: null,
|
||||
'column_info' => $columnInfo,
|
||||
'designer_settings' => $this->databaseDesignerSettingsFeature !== null
|
||||
? $this->databaseDesignerSettingsFeature->designerSettings->getName()
|
||||
: null,
|
||||
'export_templates' => $this->exportTemplatesFeature !== null
|
||||
? $this->exportTemplatesFeature->exportTemplates->getName()
|
||||
: null,
|
||||
'favorite' => $this->favoriteTablesFeature !== null
|
||||
? $this->favoriteTablesFeature->favorite->getName()
|
||||
: null,
|
||||
'history' => $this->sqlHistoryFeature !== null ? $this->sqlHistoryFeature->history->getName() : null,
|
||||
'navigationhiding' => $this->navigationItemsHidingFeature !== null
|
||||
? $this->navigationItemsHidingFeature->navigationHiding->getName()
|
||||
: null,
|
||||
'pdf_pages' => $this->pdfFeature !== null ? $this->pdfFeature->pdfPages->getName() : null,
|
||||
'recent' => $this->recentlyUsedTablesFeature !== null
|
||||
? $this->recentlyUsedTablesFeature->recent->getName()
|
||||
: null,
|
||||
'relation' => $relation,
|
||||
'savedsearches' => $this->savedQueryByExampleSearchesFeature !== null
|
||||
? $this->savedQueryByExampleSearchesFeature->savedSearches->getName()
|
||||
: null,
|
||||
'table_coords' => $this->pdfFeature !== null ? $this->pdfFeature->tableCoords->getName() : null,
|
||||
'table_info' => $this->displayFeature !== null ? $this->displayFeature->tableInfo->getName() : null,
|
||||
'table_uiprefs' => $this->uiPreferencesFeature !== null
|
||||
? $this->uiPreferencesFeature->tableUiPrefs->getName()
|
||||
: null,
|
||||
'tracking' => $this->trackingFeature !== null ? $this->trackingFeature->tracking->getName() : null,
|
||||
'userconfig' => $this->userPreferencesFeature !== null
|
||||
? $this->userPreferencesFeature->userConfig->getName()
|
||||
: null,
|
||||
'usergroups' => $this->configurableMenusFeature !== null
|
||||
? $this->configurableMenusFeature->userGroups->getName()
|
||||
: null,
|
||||
'users' => $this->configurableMenusFeature !== null
|
||||
? $this->configurableMenusFeature->users->getName()
|
||||
: null,
|
||||
'bookmarkwork' => $this->bookmarkFeature !== null,
|
||||
'mimework' => $this->browserTransformationFeature !== null,
|
||||
'centralcolumnswork' => $this->centralColumnsFeature !== null,
|
||||
'commwork' => $this->columnCommentsFeature !== null,
|
||||
'menuswork' => $this->configurableMenusFeature !== null,
|
||||
'designersettingswork' => $this->databaseDesignerSettingsFeature !== null,
|
||||
'displaywork' => $this->displayFeature !== null,
|
||||
'exporttemplateswork' => $this->exportTemplatesFeature !== null,
|
||||
'favoritework' => $this->favoriteTablesFeature !== null,
|
||||
'navwork' => $this->navigationItemsHidingFeature !== null,
|
||||
'pdfwork' => $this->pdfFeature !== null,
|
||||
'recentwork' => $this->recentlyUsedTablesFeature !== null,
|
||||
'relwork' => $this->relationFeature !== null,
|
||||
'savedsearcheswork' => $this->savedQueryByExampleSearchesFeature !== null,
|
||||
'historywork' => $this->sqlHistoryFeature !== null,
|
||||
'trackingwork' => $this->trackingFeature !== null,
|
||||
'uiprefswork' => $this->uiPreferencesFeature !== null,
|
||||
'userconfigwork' => $this->userPreferencesFeature !== null,
|
||||
'allworks' => $this->hasAllFeatures(),
|
||||
];
|
||||
}
|
||||
|
||||
public function hasAllFeatures(): bool
|
||||
{
|
||||
return $this->bookmarkFeature !== null
|
||||
&& $this->browserTransformationFeature !== null
|
||||
&& $this->centralColumnsFeature !== null
|
||||
&& $this->columnCommentsFeature !== null
|
||||
&& $this->configurableMenusFeature !== null
|
||||
&& $this->databaseDesignerSettingsFeature !== null
|
||||
&& $this->displayFeature !== null
|
||||
&& $this->exportTemplatesFeature !== null
|
||||
&& $this->favoriteTablesFeature !== null
|
||||
&& $this->navigationItemsHidingFeature !== null
|
||||
&& $this->pdfFeature !== null
|
||||
&& $this->recentlyUsedTablesFeature !== null
|
||||
&& $this->relationFeature !== null
|
||||
&& $this->savedQueryByExampleSearchesFeature !== null
|
||||
&& $this->sqlHistoryFeature !== null
|
||||
&& $this->trackingFeature !== null
|
||||
&& $this->uiPreferencesFeature !== null
|
||||
&& $this->userPreferencesFeature !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $tableName
|
||||
*/
|
||||
private static function getTableName($tableName): ?TableName
|
||||
{
|
||||
try {
|
||||
return TableName::fromValue($tableName);
|
||||
} catch (InvalidArgumentException $exception) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
350
admin/phpMyAdmin/libraries/classes/ConfigStorage/UserGroups.php
Normal file
350
admin/phpMyAdmin/libraries/classes/ConfigStorage/UserGroups.php
Normal file
|
@ -0,0 +1,350 @@
|
|||
<?php
|
||||
/**
|
||||
* set of functions for user group handling
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\ConfigStorage;
|
||||
|
||||
use PhpMyAdmin\ConfigStorage\Features\ConfigurableMenusFeature;
|
||||
use PhpMyAdmin\Html\Generator;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
|
||||
use function __;
|
||||
use function array_keys;
|
||||
use function htmlspecialchars;
|
||||
use function implode;
|
||||
use function in_array;
|
||||
use function mb_substr;
|
||||
use function substr;
|
||||
|
||||
/**
|
||||
* PhpMyAdmin\Server\UserGroups class
|
||||
*/
|
||||
class UserGroups
|
||||
{
|
||||
/**
|
||||
* Return HTML to list the users belonging to a given user group
|
||||
*
|
||||
* @param string $userGroup user group name
|
||||
*
|
||||
* @return string HTML to list the users belonging to a given user group
|
||||
*/
|
||||
public static function getHtmlForListingUsersofAGroup(
|
||||
ConfigurableMenusFeature $configurableMenusFeature,
|
||||
string $userGroup
|
||||
): string {
|
||||
global $dbi;
|
||||
|
||||
$users = [];
|
||||
$numRows = 0;
|
||||
|
||||
$userGroupSpecialChars = htmlspecialchars($userGroup);
|
||||
$usersTable = Util::backquote($configurableMenusFeature->database)
|
||||
. '.' . Util::backquote($configurableMenusFeature->users);
|
||||
$sql_query = 'SELECT `username` FROM ' . $usersTable
|
||||
. " WHERE `usergroup`='" . $dbi->escapeString($userGroup)
|
||||
. "'";
|
||||
$result = $dbi->tryQueryAsControlUser($sql_query);
|
||||
if ($result) {
|
||||
$i = 0;
|
||||
while ($row = $result->fetchRow()) {
|
||||
$users[] = [
|
||||
'count' => ++$i,
|
||||
'user' => $row[0],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$template = new Template();
|
||||
|
||||
return $template->render('server/user_groups/user_listings', [
|
||||
'user_group_special_chars' => $userGroupSpecialChars,
|
||||
'num_rows' => $numRows,
|
||||
'users' => $users,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns HTML for the 'user groups' table
|
||||
*
|
||||
* @return string HTML for the 'user groups' table
|
||||
*/
|
||||
public static function getHtmlForUserGroupsTable(ConfigurableMenusFeature $configurableMenusFeature): string
|
||||
{
|
||||
global $dbi;
|
||||
|
||||
$groupTable = Util::backquote($configurableMenusFeature->database)
|
||||
. '.' . Util::backquote($configurableMenusFeature->userGroups);
|
||||
$sql_query = 'SELECT * FROM ' . $groupTable . ' ORDER BY `usergroup` ASC';
|
||||
$result = $dbi->tryQueryAsControlUser($sql_query);
|
||||
$userGroups = [];
|
||||
$userGroupsValues = [];
|
||||
$action = Url::getFromRoute('/server/privileges');
|
||||
$hidden_inputs = null;
|
||||
if ($result && $result->numRows()) {
|
||||
$hidden_inputs = Url::getHiddenInputs();
|
||||
foreach ($result as $row) {
|
||||
$groupName = $row['usergroup'];
|
||||
if (! isset($userGroups[$groupName])) {
|
||||
$userGroups[$groupName] = [];
|
||||
}
|
||||
|
||||
$userGroups[$groupName][$row['tab']] = $row['allowed'];
|
||||
}
|
||||
|
||||
foreach ($userGroups as $groupName => $tabs) {
|
||||
$userGroupVal = [];
|
||||
$userGroupVal['name'] = htmlspecialchars((string) $groupName);
|
||||
$userGroupVal['serverTab'] = self::getAllowedTabNames($tabs, 'server');
|
||||
$userGroupVal['dbTab'] = self::getAllowedTabNames($tabs, 'db');
|
||||
$userGroupVal['tableTab'] = self::getAllowedTabNames($tabs, 'table');
|
||||
$userGroupVal['userGroupUrl'] = Url::getFromRoute('/server/user-groups');
|
||||
$userGroupVal['viewUsersUrl'] = Url::getCommon(
|
||||
[
|
||||
'viewUsers' => 1,
|
||||
'userGroup' => $groupName,
|
||||
],
|
||||
'',
|
||||
false
|
||||
);
|
||||
$userGroupVal['viewUsersIcon'] = Generator::getIcon('b_usrlist', __('View users'));
|
||||
|
||||
$userGroupVal['editUsersUrl'] = Url::getCommon(
|
||||
[
|
||||
'editUserGroup' => 1,
|
||||
'userGroup' => $groupName,
|
||||
],
|
||||
'',
|
||||
false
|
||||
);
|
||||
$userGroupVal['editUsersIcon'] = Generator::getIcon('b_edit', __('Edit'));
|
||||
$userGroupsValues[] = $userGroupVal;
|
||||
}
|
||||
}
|
||||
|
||||
$addUserUrl = Url::getFromRoute('/server/user-groups', ['addUserGroup' => 1]);
|
||||
$addUserIcon = Generator::getIcon('b_usradd');
|
||||
$template = new Template();
|
||||
|
||||
return $template->render('server/user_groups/user_groups', [
|
||||
'action' => $action,
|
||||
'hidden_inputs' => $hidden_inputs ?? '',
|
||||
'has_rows' => $userGroups !== [],
|
||||
'user_groups_values' => $userGroupsValues,
|
||||
'add_user_url' => $addUserUrl,
|
||||
'add_user_icon' => $addUserIcon,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of allowed menu tab names
|
||||
* based on a data row from usergroup table.
|
||||
*
|
||||
* @param array $row row of usergroup table
|
||||
* @param string $level 'server', 'db' or 'table'
|
||||
*
|
||||
* @return string comma separated list of allowed menu tab names
|
||||
*/
|
||||
public static function getAllowedTabNames(array $row, string $level): string
|
||||
{
|
||||
$tabNames = [];
|
||||
$tabs = Util::getMenuTabList($level);
|
||||
foreach ($tabs as $tab => $tabName) {
|
||||
if (isset($row[$level . '_' . $tab]) && $row[$level . '_' . $tab] !== 'Y') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tabNames[] = $tabName;
|
||||
}
|
||||
|
||||
return implode(', ', $tabNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a user group
|
||||
*
|
||||
* @param string $userGroup user group name
|
||||
*/
|
||||
public static function delete(ConfigurableMenusFeature $configurableMenusFeature, string $userGroup): void
|
||||
{
|
||||
global $dbi;
|
||||
|
||||
$userTable = Util::backquote($configurableMenusFeature->database)
|
||||
. '.' . Util::backquote($configurableMenusFeature->users);
|
||||
$groupTable = Util::backquote($configurableMenusFeature->database)
|
||||
. '.' . Util::backquote($configurableMenusFeature->userGroups);
|
||||
$sql_query = 'DELETE FROM ' . $userTable
|
||||
. " WHERE `usergroup`='" . $dbi->escapeString($userGroup)
|
||||
. "'";
|
||||
$dbi->queryAsControlUser($sql_query);
|
||||
$sql_query = 'DELETE FROM ' . $groupTable
|
||||
. " WHERE `usergroup`='" . $dbi->escapeString($userGroup)
|
||||
. "'";
|
||||
$dbi->queryAsControlUser($sql_query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns HTML for add/edit user group dialog
|
||||
*
|
||||
* @param string|null $userGroup name of the user group in case of editing
|
||||
*
|
||||
* @return string HTML for add/edit user group dialog
|
||||
*/
|
||||
public static function getHtmlToEditUserGroup(
|
||||
ConfigurableMenusFeature $configurableMenusFeature,
|
||||
?string $userGroup = null
|
||||
): string {
|
||||
global $dbi;
|
||||
|
||||
$urlParams = [];
|
||||
|
||||
$editUserGroupSpecialChars = '';
|
||||
if ($userGroup !== null) {
|
||||
$editUserGroupSpecialChars = htmlspecialchars($userGroup);
|
||||
}
|
||||
|
||||
if ($userGroup !== null) {
|
||||
$urlParams['userGroup'] = $userGroup;
|
||||
$urlParams['editUserGroupSubmit'] = '1';
|
||||
} else {
|
||||
$urlParams['addUserGroupSubmit'] = '1';
|
||||
}
|
||||
|
||||
$allowedTabs = [
|
||||
'server' => [],
|
||||
'db' => [],
|
||||
'table' => [],
|
||||
];
|
||||
if ($userGroup !== null) {
|
||||
$groupTable = Util::backquote($configurableMenusFeature->database)
|
||||
. '.' . Util::backquote($configurableMenusFeature->userGroups);
|
||||
$sql_query = 'SELECT * FROM ' . $groupTable
|
||||
. " WHERE `usergroup`='" . $dbi->escapeString($userGroup)
|
||||
. "'";
|
||||
$result = $dbi->tryQueryAsControlUser($sql_query);
|
||||
if ($result) {
|
||||
foreach ($result as $row) {
|
||||
$key = $row['tab'];
|
||||
$value = $row['allowed'];
|
||||
if (substr($key, 0, 7) === 'server_' && $value === 'Y') {
|
||||
$allowedTabs['server'][] = mb_substr($key, 7);
|
||||
} elseif (substr($key, 0, 3) === 'db_' && $value === 'Y') {
|
||||
$allowedTabs['db'][] = mb_substr($key, 3);
|
||||
} elseif (substr($key, 0, 6) === 'table_' && $value === 'Y') {
|
||||
$allowedTabs['table'][] = mb_substr($key, 6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unset($result);
|
||||
}
|
||||
|
||||
$tabList = self::getTabList(
|
||||
__('Server-level tabs'),
|
||||
'server',
|
||||
$allowedTabs['server']
|
||||
);
|
||||
$tabList .= self::getTabList(
|
||||
__('Database-level tabs'),
|
||||
'db',
|
||||
$allowedTabs['db']
|
||||
);
|
||||
$tabList .= self::getTabList(
|
||||
__('Table-level tabs'),
|
||||
'table',
|
||||
$allowedTabs['table']
|
||||
);
|
||||
|
||||
$template = new Template();
|
||||
|
||||
return $template->render('server/user_groups/edit_user_groups', [
|
||||
'user_group' => $userGroup,
|
||||
'edit_user_group_special_chars' => $editUserGroupSpecialChars,
|
||||
'user_group_url' => Url::getFromRoute('/server/user-groups'),
|
||||
'hidden_inputs' => Url::getHiddenInputs($urlParams),
|
||||
'tab_list' => $tabList,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns HTML for checkbox groups to choose
|
||||
* tabs of 'server', 'db' or 'table' levels.
|
||||
*
|
||||
* @param string $title title of the checkbox group
|
||||
* @param string $level 'server', 'db' or 'table'
|
||||
* @param array $selected array of selected allowed tabs
|
||||
*
|
||||
* @return string HTML for checkbox groups
|
||||
*/
|
||||
public static function getTabList(string $title, string $level, array $selected): string
|
||||
{
|
||||
$tabs = Util::getMenuTabList($level);
|
||||
$tabDetails = [];
|
||||
foreach ($tabs as $tab => $tabName) {
|
||||
$tabDetail = [];
|
||||
$tabDetail['in_array'] = (in_array($tab, $selected) ? ' checked="checked"' : '');
|
||||
$tabDetail['tab'] = $tab;
|
||||
$tabDetail['tab_name'] = $tabName;
|
||||
$tabDetails[] = $tabDetail;
|
||||
}
|
||||
|
||||
$template = new Template();
|
||||
|
||||
return $template->render('server/user_groups/tab_list', [
|
||||
'title' => $title,
|
||||
'level' => $level,
|
||||
'tab_details' => $tabDetails,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add/update a user group with allowed menu tabs.
|
||||
*
|
||||
* @param string $userGroup user group name
|
||||
* @param bool $new whether this is a new user group
|
||||
*/
|
||||
public static function edit(
|
||||
ConfigurableMenusFeature $configurableMenusFeature,
|
||||
string $userGroup,
|
||||
bool $new = false
|
||||
): void {
|
||||
global $dbi;
|
||||
|
||||
$tabs = Util::getMenuTabList();
|
||||
$groupTable = Util::backquote($configurableMenusFeature->database)
|
||||
. '.' . Util::backquote($configurableMenusFeature->userGroups);
|
||||
|
||||
if (! $new) {
|
||||
$sql_query = 'DELETE FROM ' . $groupTable
|
||||
. " WHERE `usergroup`='" . $dbi->escapeString($userGroup)
|
||||
. "';";
|
||||
$dbi->queryAsControlUser($sql_query);
|
||||
}
|
||||
|
||||
$sql_query = 'INSERT INTO ' . $groupTable
|
||||
. '(`usergroup`, `tab`, `allowed`)'
|
||||
. ' VALUES ';
|
||||
$first = true;
|
||||
/** @var array<string, string> $tabGroup */
|
||||
foreach ($tabs as $tabGroupName => $tabGroup) {
|
||||
foreach (array_keys($tabGroup) as $tab) {
|
||||
if (! $first) {
|
||||
$sql_query .= ', ';
|
||||
}
|
||||
|
||||
$tabName = $tabGroupName . '_' . $tab;
|
||||
$allowed = isset($_POST[$tabName]) && $_POST[$tabName] === 'Y';
|
||||
$sql_query .= "('" . $dbi->escapeString($userGroup) . "', '" . $tabName . "', '"
|
||||
. ($allowed ? 'Y' : 'N') . "')";
|
||||
$first = false;
|
||||
}
|
||||
}
|
||||
|
||||
$sql_query .= ';';
|
||||
$dbi->queryAsControlUser($sql_query);
|
||||
}
|
||||
}
|
139
admin/phpMyAdmin/libraries/classes/Console.php
Normal file
139
admin/phpMyAdmin/libraries/classes/Console.php
Normal file
|
@ -0,0 +1,139 @@
|
|||
<?php
|
||||
/**
|
||||
* Used to render the console of PMA's pages
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin;
|
||||
|
||||
use PhpMyAdmin\ConfigStorage\Relation;
|
||||
|
||||
use function __;
|
||||
use function _ngettext;
|
||||
use function count;
|
||||
use function sprintf;
|
||||
|
||||
/**
|
||||
* Class used to output the console
|
||||
*/
|
||||
class Console
|
||||
{
|
||||
/**
|
||||
* Whether to display anything
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $isEnabled;
|
||||
|
||||
/**
|
||||
* Whether we are servicing an ajax request.
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
public static function getBookmarkContent(): string
|
||||
{
|
||||
global $dbi;
|
||||
|
||||
$template = new Template();
|
||||
$relation = new Relation($dbi);
|
||||
$bookmarkFeature = $relation->getRelationParameters()->bookmarkFeature;
|
||||
if ($bookmarkFeature === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$bookmarks = Bookmark::getList($bookmarkFeature, $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,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of JS scripts required by console
|
||||
*
|
||||
* @return string[] list of scripts
|
||||
*/
|
||||
public function getScripts(): array
|
||||
{
|
||||
return ['console.js'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the console
|
||||
*/
|
||||
public function getDisplay(): string
|
||||
{
|
||||
if ($this->isAjax || ! $this->isEnabled) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$bookmarkFeature = $this->relation->getRelationParameters()->bookmarkFeature;
|
||||
$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', [
|
||||
'has_bookmark_feature' => $bookmarkFeature !== null,
|
||||
'image' => $image,
|
||||
'sql_history' => $_sql_history,
|
||||
'bookmark_content' => $bookmarkContent,
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers;
|
||||
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\ResponseRenderer;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
|
||||
use function __;
|
||||
use function strlen;
|
||||
|
||||
abstract class AbstractController
|
||||
{
|
||||
/** @var ResponseRenderer */
|
||||
protected $response;
|
||||
|
||||
/** @var Template */
|
||||
protected $template;
|
||||
|
||||
public function __construct(ResponseRenderer $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;
|
||||
}
|
||||
|
||||
$this->redirect('/', $params);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return $is_db;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $params
|
||||
*/
|
||||
protected function redirect(string $route, array $params = []): void
|
||||
{
|
||||
$uri = './index.php?route=' . $route . Url::getCommonRaw($params, '&');
|
||||
Core::sendHeaderLocation($uri);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers;
|
||||
|
||||
use PhpMyAdmin\BrowseForeigners;
|
||||
use PhpMyAdmin\ConfigStorage\Relation;
|
||||
use PhpMyAdmin\Http\ServerRequest;
|
||||
use PhpMyAdmin\ResponseRenderer;
|
||||
use PhpMyAdmin\Template;
|
||||
|
||||
/**
|
||||
* Display selection for relational field values
|
||||
*/
|
||||
class BrowseForeignersController extends AbstractController
|
||||
{
|
||||
/** @var BrowseForeigners */
|
||||
private $browseForeigners;
|
||||
|
||||
/** @var Relation */
|
||||
private $relation;
|
||||
|
||||
public function __construct(
|
||||
ResponseRenderer $response,
|
||||
Template $template,
|
||||
BrowseForeigners $browseForeigners,
|
||||
Relation $relation
|
||||
) {
|
||||
parent::__construct($response, $template);
|
||||
$this->browseForeigners = $browseForeigners;
|
||||
$this->relation = $relation;
|
||||
}
|
||||
|
||||
public function __invoke(ServerRequest $request): void
|
||||
{
|
||||
/** @var string|null $database */
|
||||
$database = $request->getParsedBodyParam('db');
|
||||
/** @var string|null $table */
|
||||
$table = $request->getParsedBodyParam('table');
|
||||
/** @var string|null $field */
|
||||
$field = $request->getParsedBodyParam('field');
|
||||
/** @var string $fieldKey */
|
||||
$fieldKey = $request->getParsedBodyParam('fieldkey', '');
|
||||
/** @var string $data */
|
||||
$data = $request->getParsedBodyParam('data', '');
|
||||
/** @var string|null $foreignShowAll */
|
||||
$foreignShowAll = $request->getParsedBodyParam('foreign_showAll');
|
||||
/** @var string $foreignFilter */
|
||||
$foreignFilter = $request->getParsedBodyParam('foreign_filter', '');
|
||||
|
||||
if (! isset($database, $table, $field)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response->getFooter()->setMinimal();
|
||||
$header = $this->response->getHeader();
|
||||
$header->disableMenuAndConsole();
|
||||
$header->setBodyId('body_browse_foreigners');
|
||||
|
||||
$foreigners = $this->relation->getForeigners($database, $table);
|
||||
$foreignLimit = $this->browseForeigners->getForeignLimit($foreignShowAll);
|
||||
$foreignData = $this->relation->getForeignData(
|
||||
$foreigners,
|
||||
$field,
|
||||
true,
|
||||
$foreignFilter,
|
||||
$foreignLimit ?? '',
|
||||
true
|
||||
);
|
||||
|
||||
$this->response->addHTML($this->browseForeigners->getHtmlForRelationalFieldSelection(
|
||||
$database,
|
||||
$table,
|
||||
$field,
|
||||
$foreignData,
|
||||
$fieldKey,
|
||||
$data
|
||||
));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
<?php
|
||||
/**
|
||||
* Simple script to set correct charset for changelog
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers;
|
||||
|
||||
use function __;
|
||||
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 __invoke(): 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,69 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers;
|
||||
|
||||
use PhpMyAdmin\ConfigStorage\Relation;
|
||||
use PhpMyAdmin\Http\ServerRequest;
|
||||
use PhpMyAdmin\ResponseRenderer;
|
||||
use PhpMyAdmin\Template;
|
||||
|
||||
use const SQL_DIR;
|
||||
|
||||
/**
|
||||
* Displays status of phpMyAdmin configuration storage
|
||||
*/
|
||||
class CheckRelationsController extends AbstractController
|
||||
{
|
||||
/** @var Relation */
|
||||
private $relation;
|
||||
|
||||
public function __construct(ResponseRenderer $response, Template $template, Relation $relation)
|
||||
{
|
||||
parent::__construct($response, $template);
|
||||
$this->relation = $relation;
|
||||
}
|
||||
|
||||
public function __invoke(ServerRequest $request): void
|
||||
{
|
||||
global $db, $cfg;
|
||||
|
||||
/** @var string|null $createPmaDb */
|
||||
$createPmaDb = $request->getParsedBodyParam('create_pmadb');
|
||||
/** @var string|null $fixAllPmaDb */
|
||||
$fixAllPmaDb = $request->getParsedBodyParam('fixall_pmadb');
|
||||
/** @var string|null $fixPmaDb */
|
||||
$fixPmaDb = $request->getParsedBodyParam('fix_pmadb');
|
||||
|
||||
$cfgStorageDbName = $this->relation->getConfigurationStorageDbName();
|
||||
|
||||
// If request for creating the pmadb
|
||||
if (isset($createPmaDb) && $this->relation->createPmaDatabase($cfgStorageDbName)) {
|
||||
$this->relation->fixPmaTables($cfgStorageDbName);
|
||||
}
|
||||
|
||||
// If request for creating all PMA tables.
|
||||
if (isset($fixAllPmaDb)) {
|
||||
$this->relation->fixPmaTables($db);
|
||||
}
|
||||
|
||||
// If request for creating missing PMA tables.
|
||||
if (isset($fixPmaDb)) {
|
||||
$relationParameters = $this->relation->getRelationParameters();
|
||||
$this->relation->fixPmaTables((string) $relationParameters->db);
|
||||
}
|
||||
|
||||
// Do not use any previous $relationParameters value as it could have changed after a successful fixPmaTables()
|
||||
$relationParameters = $this->relation->getRelationParameters();
|
||||
|
||||
$this->render('relation/check_relations', [
|
||||
'db' => $db,
|
||||
'zero_conf' => $cfg['ZeroConf'],
|
||||
'relation_parameters' => $relationParameters->toArray(),
|
||||
'sql_dir' => SQL_DIR,
|
||||
'config_storage_database_name' => $cfgStorageDbName,
|
||||
'are_config_storage_tables_defined' => $this->relation->arePmadbTablesDefined(),
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers;
|
||||
|
||||
use PhpMyAdmin\Config;
|
||||
use PhpMyAdmin\ResponseRenderer;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
|
||||
final class CollationConnectionController extends AbstractController
|
||||
{
|
||||
/** @var Config */
|
||||
private $config;
|
||||
|
||||
public function __construct(ResponseRenderer $response, Template $template, Config $config)
|
||||
{
|
||||
parent::__construct($response, $template);
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
public function __invoke(): void
|
||||
{
|
||||
$this->config->setUserValue(
|
||||
null,
|
||||
'DefaultConnectionCollation',
|
||||
$_POST['collation_connection'],
|
||||
'utf8mb4_unicode_ci'
|
||||
);
|
||||
|
||||
$this->response->header('Location: index.php?route=/' . Url::getCommonRaw([], '&'));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers;
|
||||
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Http\ServerRequest;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\ResponseRenderer;
|
||||
use PhpMyAdmin\Template;
|
||||
|
||||
final class ColumnController extends AbstractController
|
||||
{
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
public function __construct(ResponseRenderer $response, Template $template, DatabaseInterface $dbi)
|
||||
{
|
||||
parent::__construct($response, $template);
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function __invoke(ServerRequest $request): void
|
||||
{
|
||||
/** @var string|null $db */
|
||||
$db = $request->getParsedBodyParam('db');
|
||||
/** @var string|null $table */
|
||||
$table = $request->getParsedBodyParam('table');
|
||||
|
||||
if (! isset($db, $table)) {
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON(['message' => Message::error()]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response->addJSON(['columns' => $this->dbi->getColumnNames($db, $table)]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Config;
|
||||
|
||||
use PhpMyAdmin\Config;
|
||||
use PhpMyAdmin\Controllers\AbstractController;
|
||||
use PhpMyAdmin\Http\ServerRequest;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\ResponseRenderer;
|
||||
use PhpMyAdmin\Template;
|
||||
|
||||
final class GetConfigController extends AbstractController
|
||||
{
|
||||
/** @var Config */
|
||||
private $config;
|
||||
|
||||
public function __construct(ResponseRenderer $response, Template $template, Config $config)
|
||||
{
|
||||
parent::__construct($response, $template);
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
public function __invoke(ServerRequest $request): void
|
||||
{
|
||||
/** @var string|null $key */
|
||||
$key = $request->getParsedBodyParam('key');
|
||||
|
||||
if (! isset($key)) {
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON(['message' => Message::error()]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response->addJSON(['value' => $this->config->get($key)]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Config;
|
||||
|
||||
use PhpMyAdmin\Config;
|
||||
use PhpMyAdmin\Controllers\AbstractController;
|
||||
use PhpMyAdmin\Http\ServerRequest;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\ResponseRenderer;
|
||||
use PhpMyAdmin\Template;
|
||||
|
||||
use function json_decode;
|
||||
|
||||
final class SetConfigController extends AbstractController
|
||||
{
|
||||
/** @var Config */
|
||||
private $config;
|
||||
|
||||
public function __construct(ResponseRenderer $response, Template $template, Config $config)
|
||||
{
|
||||
parent::__construct($response, $template);
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
public function __invoke(ServerRequest $request): void
|
||||
{
|
||||
/** @var string|null $key */
|
||||
$key = $request->getParsedBodyParam('key');
|
||||
/** @var string|null $value */
|
||||
$value = $request->getParsedBodyParam('value');
|
||||
|
||||
if (! isset($key, $value)) {
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON(['message' => Message::error()]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$result = $this->config->setUserValue(null, $key, json_decode($value));
|
||||
|
||||
if ($result === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON(['message' => $result]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Database;
|
||||
|
||||
use PhpMyAdmin\Controllers\AbstractController as Controller;
|
||||
use PhpMyAdmin\ResponseRenderer;
|
||||
use PhpMyAdmin\Template;
|
||||
|
||||
abstract class AbstractController extends Controller
|
||||
{
|
||||
/** @var string */
|
||||
protected $db;
|
||||
|
||||
public function __construct(ResponseRenderer $response, Template $template, string $db)
|
||||
{
|
||||
parent::__construct($response, $template);
|
||||
$this->db = $db;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Database\CentralColumns;
|
||||
|
||||
use PhpMyAdmin\Controllers\Database\AbstractController;
|
||||
use PhpMyAdmin\Database\CentralColumns;
|
||||
use PhpMyAdmin\ResponseRenderer;
|
||||
use PhpMyAdmin\Template;
|
||||
|
||||
final class PopulateColumnsController extends AbstractController
|
||||
{
|
||||
/** @var CentralColumns */
|
||||
private $centralColumns;
|
||||
|
||||
public function __construct(
|
||||
ResponseRenderer $response,
|
||||
Template $template,
|
||||
string $db,
|
||||
CentralColumns $centralColumns
|
||||
) {
|
||||
parent::__construct($response, $template, $db);
|
||||
$this->centralColumns = $centralColumns;
|
||||
}
|
||||
|
||||
public function __invoke(): void
|
||||
{
|
||||
$columns = $this->centralColumns->getColumnsNotInCentralList($this->db, $_POST['selectedTable']);
|
||||
$this->render('database/central_columns/populate_columns', ['columns' => $columns]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,280 @@
|
|||
<?php
|
||||
/**
|
||||
* Central Columns view/edit
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Database;
|
||||
|
||||
use PhpMyAdmin\Database\CentralColumns;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\ResponseRenderer;
|
||||
use PhpMyAdmin\Template;
|
||||
|
||||
use function __;
|
||||
use function is_bool;
|
||||
use function is_numeric;
|
||||
use function parse_str;
|
||||
use function sprintf;
|
||||
|
||||
class CentralColumnsController extends AbstractController
|
||||
{
|
||||
/** @var CentralColumns */
|
||||
private $centralColumns;
|
||||
|
||||
public function __construct(
|
||||
ResponseRenderer $response,
|
||||
Template $template,
|
||||
string $db,
|
||||
CentralColumns $centralColumns
|
||||
) {
|
||||
parent::__construct($response, $template, $db);
|
||||
$this->centralColumns = $centralColumns;
|
||||
}
|
||||
|
||||
public function __invoke(): 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 (isset($_POST['pos']) && is_numeric($_POST['pos'])) {
|
||||
$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;
|
||||
|
||||
if (! empty($params['total_rows']) && is_numeric($params['total_rows'])) {
|
||||
$totalRows = (int) $params['total_rows'];
|
||||
} else {
|
||||
$totalRows = $this->centralColumns->getCount($this->db);
|
||||
}
|
||||
|
||||
$pos = 0;
|
||||
if (isset($params['pos']) && is_numeric($params['pos'])) {
|
||||
$pos = (int) $params['pos'];
|
||||
}
|
||||
|
||||
$variables = $this->centralColumns->getTemplateVariablesForMain($this->db, $totalRows, $pos, $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'] ?? '');
|
||||
}
|
||||
|
||||
/**
|
||||
* @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,122 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Database;
|
||||
|
||||
use PhpMyAdmin\ConfigStorage\Relation;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Index;
|
||||
use PhpMyAdmin\ResponseRenderer;
|
||||
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;
|
||||
|
||||
public function __construct(
|
||||
ResponseRenderer $response,
|
||||
Template $template,
|
||||
string $db,
|
||||
Relation $relation,
|
||||
Transformations $transformations,
|
||||
DatabaseInterface $dbi
|
||||
) {
|
||||
parent::__construct($response, $template, $db);
|
||||
$this->relation = $relation;
|
||||
$this->transformations = $transformations;
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function __invoke(): void
|
||||
{
|
||||
Util::checkParameters(['db'], true);
|
||||
|
||||
$relationParameters = $this->relation->getRelationParameters();
|
||||
|
||||
$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(
|
||||
$relationParameters->relationFeature !== null,
|
||||
$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 ($relationParameters->browserTransformationFeature !== null) {
|
||||
$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' => $relationParameters->browserTransformationFeature !== null,
|
||||
'columns' => $rows,
|
||||
'indexes' => Index::getFromTable($tableName, $this->db),
|
||||
];
|
||||
}
|
||||
|
||||
$this->render('database/data_dictionary/index', [
|
||||
'database' => $this->db,
|
||||
'comment' => $comment,
|
||||
'tables' => $tables,
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,250 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Database;
|
||||
|
||||
use PhpMyAdmin\Database\Designer;
|
||||
use PhpMyAdmin\Database\Designer\Common as DesignerCommon;
|
||||
use PhpMyAdmin\ResponseRenderer;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
|
||||
use function __;
|
||||
use function htmlspecialchars;
|
||||
use function in_array;
|
||||
use function sprintf;
|
||||
|
||||
class DesignerController extends AbstractController
|
||||
{
|
||||
/** @var Designer */
|
||||
private $databaseDesigner;
|
||||
|
||||
/** @var DesignerCommon */
|
||||
private $designerCommon;
|
||||
|
||||
public function __construct(
|
||||
ResponseRenderer $response,
|
||||
Template $template,
|
||||
string $db,
|
||||
Designer $databaseDesigner,
|
||||
DesignerCommon $designerCommon
|
||||
) {
|
||||
parent::__construct($response, $template, $db);
|
||||
$this->databaseDesigner = $databaseDesigner;
|
||||
$this->designerCommon = $designerCommon;
|
||||
}
|
||||
|
||||
public function __invoke(): 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, $errorUrl;
|
||||
|
||||
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',
|
||||
sprintf(
|
||||
/* l10n: The user tries to save a page with an existing name in Designer */
|
||||
__('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']);
|
||||
|
||||
$errorUrl = Util::getScriptNameForOption($cfg['DefaultTabDatabase'], 'database');
|
||||
$errorUrl .= 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([
|
||||
'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,86 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Database;
|
||||
|
||||
use PhpMyAdmin\Database\Events;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\ResponseRenderer;
|
||||
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;
|
||||
|
||||
public function __construct(
|
||||
ResponseRenderer $response,
|
||||
Template $template,
|
||||
string $db,
|
||||
Events $events,
|
||||
DatabaseInterface $dbi
|
||||
) {
|
||||
parent::__construct($response, $template, $db);
|
||||
$this->events = $events;
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function __invoke(): void
|
||||
{
|
||||
global $db, $tables, $num_tables, $total_num_tables, $sub_part, $errors, $text_dir;
|
||||
global $tooltip_truename, $tooltip_aliasname, $pos, $cfg, $errorUrl;
|
||||
|
||||
$this->addScriptFiles(['database/events.js']);
|
||||
|
||||
if (! $this->response->isAjax()) {
|
||||
Util::checkParameters(['db']);
|
||||
|
||||
$errorUrl = Util::getScriptNameForOption($cfg['DefaultTabDatabase'], 'database');
|
||||
$errorUrl .= 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,
|
||||
'has_privilege' => Util::currentUserHasPrivilege('EVENT', $db),
|
||||
'scheduler_state' => $this->events->getEventSchedulerStatus(),
|
||||
'text_dir' => $text_dir,
|
||||
'is_ajax' => $this->response->isAjax() && empty($_REQUEST['ajax_page_request']),
|
||||
]);
|
||||
}
|
||||
}
|
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