Compare commits

..

185 Commits

Author SHA1 Message Date
Jakub Vrana
5fdcfd0978 Release 5.1.1 2025-04-02 19:56:11 +02:00
Jakub Vrana
5b7dfbec11 Select: Allow ordering by COUNT(*) (fix #966, regression from 5.0.2) 2025-04-02 18:01:14 +02:00
Jakub Vrana
76dd19b69f Japanese: Update message 2025-04-02 17:41:40 +02:00
Takashi SHIRAI
104edc75fa Plugins autoloading: Modify the Japanese message.
Signed-off-by: Takashi SHIRAI <shirai@nintendo.co.jp>
2025-04-02 17:35:12 +02:00
Jakub Vrana
bd85f19a44 AdminerBeforeUnload: No error for Ctrl+Enter 2025-04-01 20:56:35 +02:00
Jakub Vrana
2db83e9b8f Fix JS error on Ctrl+Shift+Enter on select= 2025-04-01 20:45:39 +02:00
Jakub Vrana
5e3990e473 Turkish: Fix date hint (thanks Shirai Takashi) 2025-04-01 20:27:37 +02:00
Takashi SHIRAI
3e9d47ad08 Modify the Japanese messages.
Signed-off-by: Takashi SHIRAI <shirai@nintendo.co.jp>
2025-04-01 20:25:11 +02:00
Jakub Vrana
04ed73be26 Explicitly mark nullable params (thanks to @dg) 2025-04-01 19:14:44 +02:00
Jakub Vrana
01ea001f22 Explicitly mark nullable params (thanks to @dg) 2025-04-01 19:09:46 +02:00
Jakub Vrana
634b0aaacf AdminerLoginServers: Add comment (bug #965) 2025-04-01 18:55:34 +02:00
Jakub Vrana
06469660e8 Login: Fix hiding server with AdminerLoginServers 2025-04-01 18:27:04 +02:00
Jakub Vrana
e0629c6445 Fix lang.php 2025-04-01 17:21:32 +02:00
makss
1f58f664ae Update Ukrainian and Russian translation 2025-04-01 17:21:23 +02:00
Jakub Vrana
d9956c8a5c New plugin: Use Monaco Editor for syntax highlighting 2025-04-01 16:37:12 +02:00
Jakub Vrana
a03b05ceb4 AdminerPrism: Add border and resize 2025-04-01 12:31:45 +02:00
Jakub Vrana
b386463dcf AdminerPrism: Use Code Editor for highlighting 2025-04-01 12:10:44 +02:00
Jakub Vrana
954cc17312 AdminerPrism: Add Code Editor 2025-04-01 11:57:50 +02:00
Jakub Vrana
45a68bd6f7 New plugin: Use Prism for syntax highlighting 2025-04-01 10:00:59 +02:00
Jakub Vrana
9ec24b9244 Syntax highlighting: Hook AJAX 2025-04-01 09:52:04 +02:00
Jakub Vrana
717f0b0e10 AdminerCodemirror: Use latest version 2025-04-01 07:33:46 +02:00
Jakub Vrana
a27f0953a6 AdminerCodemirror: Use allFields (bug #962) 2025-03-31 21:45:06 +02:00
Jakub Vrana
177429d59f Optimize retrieving columns for schema 2025-03-31 21:45:06 +02:00
Jakub Vrana
9f3f3b9515 Plugins: Allow changing CSP by more plugins 2025-03-31 20:20:26 +02:00
Jakub Vrana
595c228175 AdminerCodemirror: Use jsDelivr by default 2025-03-31 20:17:30 +02:00
Jakub Vrana
058a9ec2ce Tests: Add comment 2025-03-31 19:42:34 +02:00
Jakub Vrana
7f6ae00b0d AdminerSqlGemini: Empty query 2025-03-31 19:37:27 +02:00
Jakub Vrana
cfde891ea4 Unify textarea highlighting 2025-03-31 19:30:23 +02:00
Jakub Vrana
63ab8561be Auth: Set token after unsuccessful login
Broken by d59830c
2025-03-31 18:40:18 +02:00
Jakub Vrana
5b095e9f4e SQL: Stop session 2025-03-31 18:39:02 +02:00
Jakub Vrana
b2fb3587fd AdminerForeignSystem: Add more views 2025-03-31 18:38:46 +02:00
Jakub Vrana
5cfd3f422c Select: Align numeric null right 2025-03-31 17:14:02 +02:00
Jakub Vrana
eb6b23e014 Fix type when missing $field
This happens e.g. for INNODB_BUFFER_PAGE.IS_STALE which is undeclared but returned.
2025-03-31 17:07:27 +02:00
Jakub Vrana
10bc856ebe Call static method 2025-03-31 12:35:45 +02:00
Jakub Vrana
7dd214a03c Use a helper 2025-03-31 12:24:27 +02:00
Jakub Vrana
2504ea23c4 Tests: Fix MySQL PDO 2025-03-31 11:23:43 +02:00
Jakub Vrana
e6c0c8ab6b Fix type of select_db() 2025-03-31 11:08:37 +02:00
Jakub Vrana
366342985d Tests: Fix generating PDO after c7140c2 2025-03-31 10:50:17 +02:00
Jakub Vrana
27c688b902 SQLite: Do not return unrelated auto_increment 2025-03-31 10:42:53 +02:00
Jakub Vrana
41964badb0 SQLite: Fix type of $auto_increment 2025-03-31 10:42:30 +02:00
Jakub Vrana
c76b4f1805 Update PhpShrink 2025-03-31 10:23:03 +02:00
Jakub Vrana
1b52d3a975 Localize help links 2025-03-31 10:18:17 +02:00
Jakub Vrana
695ce8c4da Use Lang::$translations instead of $translations 2025-03-31 10:18:14 +02:00
Jakub Vrana
30a8c4caca MS SQL: Fix type 2025-03-31 10:09:31 +02:00
Jakub Vrana
21347e6ef5 Initialize optional variable 2025-03-31 10:09:31 +02:00
Jakub Vrana
f2871266ad Tests: Fix 2025-03-31 10:09:31 +02:00
Jakub Vrana
79bebe77ba AdminerForeignSystem: Support new tables 2025-03-31 10:09:30 +02:00
Jakub Vrana
007c97d0d2 Fix converting long values to unique_idf
Found by PHP error: Trying to access array offset on null
2025-03-31 10:09:30 +02:00
Jakub Vrana
b50d19629f PHPStan: Use int for $limit 2025-03-31 10:09:30 +02:00
Jakub Vrana
016c1b2357 PHPStan: Fix types 2025-03-31 10:09:30 +02:00
Jakub Vrana
c05b1ac048 Tests: Add screenshots 2025-03-31 10:09:30 +02:00
Jakub Vrana
c64ee3d907 PostgreSQL: Fix login 2025-03-31 10:09:30 +02:00
Jakub Vrana
b64c80acc9 Schema: Reduce precision to pixels 2025-03-31 10:09:30 +02:00
Matrixman
fa22df0d7f New version of design rmSOFT
New version of design rmSOFT
2025-03-31 10:09:30 +02:00
Jakub Vrana
a93e4cb694 Return Db from connection()
It's not a real type declaration because compile.php passes stdClass here.
2025-03-31 10:09:30 +02:00
Jakub Vrana
7ee6f4f7ac Move connect() to Driver 2025-03-31 10:09:30 +02:00
Jakub Vrana
992561f75e Add comment 2025-03-31 10:09:30 +02:00
Jakub Vrana
65fd673d05 Remove ignored errors 2025-03-31 10:09:30 +02:00
Jakub Vrana
4b262ededa Remove unnecessary braces 2025-03-31 10:09:29 +02:00
Jakub Vrana
0d67bd9eb8 Update docs 2025-03-31 10:09:29 +02:00
Jakub Vrana
9b6943d5af Add helper for $connection2 2025-03-29 22:21:18 +01:00
Jakub Vrana
291ae7f1ac Fix types of $connection2 2025-03-29 22:17:16 +01:00
Jakub Vrana
712d96b22c Use connection() instead of $connection 2025-03-29 22:10:20 +01:00
Jakub Vrana
168ea5ae6d Use driver() instead of $driver 2025-03-29 22:05:31 +01:00
Jakub Vrana
845445baad Use adminer() instead of $adminer 2025-03-29 21:43:29 +01:00
Jakub Vrana
87f149ce1d Move HTML function, fix types 2025-03-29 18:10:32 +01:00
Jakub Vrana
79f5280f3d AdminerCodemirror: Simplify code 2025-03-29 17:50:43 +01:00
Jakub Vrana
82450b1ad2 Changelog: Use bullets 2025-03-29 17:00:15 +01:00
Jakub Vrana
eeb13253a8 Add comment 2025-03-29 16:25:35 +01:00
Jakub Vrana
6a3161cd49 Fix type of permanentLogin() 2025-03-29 15:21:25 +01:00
Jakub Vrana
141db3cb8d Add comment 2025-03-29 15:17:39 +01:00
Jakub Vrana
fd1661d811 Doc: Move type stripping 2025-03-29 15:01:51 +01:00
Jakub Vrana
6ea34e3b9a CSS: Add --fg color 2025-03-29 14:51:25 +01:00
Jakub Vrana
e4ed78ff7a Designs: Define --bg 2025-03-29 13:15:14 +01:00
Jakub Vrana
262366b120 CSS: Use --bg 2025-03-29 11:31:45 +01:00
Jakub Vrana
79fbf9c58a CSS: Hide menu on mobile 2025-03-29 11:11:57 +01:00
Jakub Vrana
6cf3d5d2b8 Inline GIF to CSS 2025-03-29 08:49:51 +01:00
Jakub Vrana
5b329ae720 JUSH: Fix opening help to new window 2025-03-28 23:29:14 +01:00
Jakub Vrana
225b6671c7 Delete function moved to get_val 2025-03-28 23:00:47 +01:00
Jakub Vrana
cec6db144f Compile: Fix single driver 2025-03-28 22:59:58 +01:00
Jakub Vrana
aceb4ce7a5 Move $drivers to SqlDriver 2025-03-28 22:58:32 +01:00
Jakub Vrana
1f88485a3c Rename variable 2025-03-28 22:58:03 +01:00
Jakub Vrana
4e1e638f98 MySQLi: Use default credentials
Accidentally removed by c96894e.
2025-03-28 22:29:00 +01:00
Jakub Vrana
03d0daff5c Remove global $permanent 2025-03-28 22:29:00 +01:00
Jakub Vrana
1eb7538e8c PHPStan: Mute LANG not found 2025-03-28 22:29:00 +01:00
Jakub Vrana
29339c5223 Db: Unify connection error handling 2025-03-28 22:28:52 +01:00
Jakub Vrana
d5bba383ea Check numeric table names after error 2025-03-28 20:45:27 +01:00
Jakub Vrana
d59830c7b2 Delete $has_token 2025-03-28 20:40:28 +01:00
Jakub Vrana
ff37ac1d35 MySQLi: Check for more results (fix #955) 2025-03-28 20:13:06 +01:00
Jakub Vrana
75c94cec6b Fix types 2025-03-28 19:48:25 +01:00
Jakub Vrana
65d97caeb9 Move $error to Adminer::$error 2025-03-28 19:48:23 +01:00
Jakub Vrana
3cfae4b8f4 Pass $error as param 2025-03-28 19:04:13 +01:00
Jakub Vrana
e219ef9ad1 Move $token to get_token() 2025-03-28 19:04:11 +01:00
Jakub Vrana
74457f0895 Move $HTTPS to HTTPS 2025-03-28 18:39:30 +01:00
Jakub Vrana
f6d311457e Move $langs to langs() 2025-03-28 18:39:30 +01:00
Jakub Vrana
adab18da78 Bump Composer PHP version 2025-03-28 18:39:30 +01:00
Jakub Vrana
508baa8c6b Move $LANG and get_lang() to LANG 2025-03-28 18:39:30 +01:00
Jakub Vrana
f0920af6b7 Move $VERSION and version() to VERSION 2025-03-28 18:39:30 +01:00
Jakub Vrana
06f0a926dd Docs: update 2025-03-28 18:39:28 +01:00
Jakub Vrana
81c5ae33ab Docs: wrap 2025-03-28 17:43:13 +01:00
Jakub Vrana
4cbe50fd49 Tests: select with where and order 2025-03-28 17:43:13 +01:00
Jakub Vrana
2396397b75 Elasticsearch: Make it work with Elasticsearch 8 2025-03-28 17:43:11 +01:00
Jakub Vrana
bd823716fc Elastic: Fix types 2025-03-28 16:17:26 +01:00
Jakub Vrana
7a19fa67fd Integrate Db::result in get_val 2025-03-28 15:41:38 +01:00
Jakub Vrana
195341d075 Split editFunctions 2025-03-28 15:41:38 +01:00
Jakub Vrana
46f6a96c95 Doc-comments: Fix type errors 2025-03-28 15:41:36 +01:00
Jakub Vrana
dc38a7ded3 Plugins: Move operators to a method 2025-03-28 14:30:00 +01:00
Jakub Vrana
c7140c2158 Tests: Run from /adminer/
To run them on the compiled version, rename adminer.php to index.php and start a web server one directory up.
2025-03-28 12:51:45 +01:00
Jakub Vrana
c2c8992dd0 PHPStan: Fix errors in Plugins 2025-03-28 12:47:09 +01:00
Jakub Vrana
a691bcbf15 Rename function with the same name as Driver::select 2025-03-28 12:47:09 +01:00
Jakub Vrana
e3a4a214e6 Doc-comments: Remove redundant info 2025-03-28 12:47:09 +01:00
Jakub Vrana
a9143ccbdc Doc-comments: Fix type errors 2025-03-28 12:47:09 +01:00
Jakub Vrana
c169c55d70 Call Plugins from Adminer class 2025-03-28 12:47:09 +01:00
Jakub Vrana
54f3437a6a Plugins: Simplify calling
The class Plugins don't extend Adminer anymore. Adminer is now treated like any other plugin.
Hooks are now registered in the constructor and they are called dynamically.
Apart from being much more pleasant to work with, it shaves 7 kB from the compiled file.
2025-03-28 12:47:09 +01:00
Jakub Vrana
96178b83ad Compile: Strip types 2025-03-28 12:47:08 +01:00
Jakub Vrana
b948f77af4 Doc-comments: Fix type errors 2025-03-28 12:47:06 +01:00
Jakub Vrana
54f8d731b3 Doc-comments: Sync method signatures 2025-03-28 12:45:02 +01:00
Jakub Vrana
ab4208dcb8 Doc-comments: Declare type properties 2025-03-28 12:45:02 +01:00
Jakub Vrana
5e88dae4e2 Doc-comments: Format 2025-03-28 12:45:02 +01:00
Jakub Vrana
45c045382a Doc-comments: Move return types to declaration 2025-03-28 12:45:02 +01:00
Jakub Vrana
641ee4ff26 Doc-comments: Move param types to declaration 2025-03-28 12:45:02 +01:00
Jakub Vrana
69073d9d54 AdminerLoginSsl: Document type 2025-03-28 12:45:02 +01:00
Jakub Vrana
911f3b71b7 Doc-comments: Add param names 2025-03-28 12:45:02 +01:00
Jakub Vrana
3bde36b68e Tests: Invalid table 2025-03-28 12:45:01 +01:00
Jakub Vrana
d47d3cb4c5 Document error revealed by PHPStan 2025-03-27 21:05:32 +01:00
Jakub Vrana
0cdc18d22c Tests PostgreSQL: Sequence and schema 2025-03-27 21:05:32 +01:00
Jakub Vrana
47c533db4d Test wrong password 2025-03-27 21:05:32 +01:00
Jakub Vrana
feaed0497a Tests: Add test adder 2025-03-27 21:05:32 +01:00
Jakub Vrana
104132de36 Fix errors discovered by tests 2025-03-27 21:05:31 +01:00
Jakub Vrana
4d22e8fd4e PostgreSQL: Fix PHP warning when creating new routine 2025-03-27 18:56:10 +01:00
Jakub Vrana
a2ff6a7fb1 PostgreSQL: Unuse deleted fetch_field 2025-03-27 18:47:05 +01:00
Jakub Vrana
b23bf6c055 PHPStan: Fix more errors 2025-03-27 18:39:48 +01:00
Jakub Vrana
e2deed9a02 Use common parent for Db 2025-03-27 18:39:47 +01:00
Jakub Vrana
0578b5c490 JS: Add 'use strict' 2025-03-27 10:27:46 +01:00
Jakub Vrana
81ae16bce1 JS: Remove forgotten log 2025-03-27 07:23:34 +01:00
Jakub Vrana
806aa51f48 AdminerSqlGemini: Make work with CodeMirror 2025-03-27 07:22:51 +01:00
Jakub Vrana
8f2a829b2e WYMeditor not updated since 2014 2025-03-27 07:13:17 +01:00
Jakub Vrana
36b44248aa Travis is not free anymore 2025-03-27 07:00:00 +01:00
Jakub Vrana
23f5d64d75 Compile: Fix pgsql (fix #956) 2025-03-26 22:23:58 +01:00
Jakub Vrana
584d04b5b3 PHPStan: Check level 8 without level 7 2025-03-26 21:54:17 +01:00
Jakub Vrana
d3b53d9d9c PHPStan: Fix level 6 errors 2025-03-26 21:54:00 +01:00
Jakub Vrana
c96894ecd4 PHPStan: Fix level 5 errors 2025-03-26 19:29:50 +01:00
Jakub Vrana
c78299a3f6 PHPStan: Fix level 4 errors 2025-03-26 18:32:45 +01:00
Jakub Vrana
53d5e7b60a PHPStan: Check only one driver 2025-03-26 18:21:02 +01:00
Jakub Vrana
d77ed18842 Separate queries(null) 2025-03-26 17:04:30 +01:00
Jakub Vrana
309fdb0d86 PHPStan: Fix level 3 errors 2025-03-26 16:57:58 +01:00
Jakub Vrana
7e5757f8b4 PHPStan: Fix level 2 errors 2025-03-26 16:22:15 +01:00
Jakub Vrana
d39cc24c61 PHPStan: Fix level 1 errors 2025-03-26 13:49:11 +01:00
Jakub Vrana
3de9b23156 PHPStan: Use @return void
PHPStan then warns abouts using the return value
2025-03-26 13:14:12 +01:00
Jakub Vrana
63c258a7f9 PHPStan: Fix level 0 errors 2025-03-26 13:14:10 +01:00
Jakub Vrana
f75f0aacfe SQLite: Fix non-PDO driver after 99163fe 2025-03-26 11:39:06 +01:00
Jakub Vrana
a60e00bf72 Use Adminer\Plugins 2025-03-26 11:10:37 +01:00
Jakub Vrana
109b0df6de Readme: Remove duplicite information 2025-03-26 10:34:11 +01:00
Jakub Vrana
6e7158537f Add comment 2025-03-26 10:25:13 +01:00
Jakub Vrana
c5f87110ff Notices: Use idx() 2025-03-26 10:21:36 +01:00
Jakub Vrana
1a2ae0e29e AdminerDarkSwitcher: Work with compiled version (bug #926) 2025-03-26 07:28:47 +01:00
Jakub Vrana
1b8a428d2f Notices: Avoid accessing offset on null
Thanks to @peterpp at 62017e3.
2025-03-26 07:20:10 +01:00
Jakub Vrana
d3be21e000 Tests: Add schema 2025-03-26 07:18:46 +01:00
Jakub Vrana
012562571a MySQL: Simplify condition in fk_support 2025-03-26 03:29:22 +01:00
Jakub Vrana
41aad5bc37 Doc-comment: Use type aliases for arrays
Type aliases could be defined either globally (https://phpstan.org/writing-php-code/phpdoc-types#global-type-aliases) or just for a class.
I prefer having them at the place where they are created.
2025-03-26 02:43:08 +01:00
Jakub Vrana
cd5ccddd22 Display error for invalid table 2025-03-26 01:37:53 +01:00
Jakub Vrana
cccc784da4 Always return array from table_status() 2025-03-26 01:34:48 +01:00
Jakub Vrana
cd207238b7 Move icons to CSS 2025-03-26 00:08:16 +01:00
Jakub Vrana
67fa4c2a6f Schema: Move style to CSS 2025-03-25 22:35:31 +01:00
Jakub Vrana
eac7d042ed Avoid <optgroup> in <datalist> 2025-03-25 22:04:19 +01:00
Jakub Vrana
a2077070af CSS: Invert icons in dark mode 2025-03-25 21:47:25 +01:00
Jakub Vrana
26adca1003 Simplify designs.php 2025-03-25 21:10:02 +01:00
Jakub Vrana
76d810faca lucas-sandery design: Icons with uncompiled version (fix #954) 2025-03-25 21:07:34 +01:00
Jakub Vrana
db0e44221b Doc-comments: Use special PHPStan types 2025-03-25 15:18:47 +01:00
Jakub Vrana
01e2fe4234 Doc-comments: Use array shapes in @return
This uses https://phpstan.org/writing-php-code/phpdoc-types#array-shapes
I'm not going to do this in @param, it would be better to use https://phpstan.org/writing-php-code/phpdoc-types#global-type-aliases
2025-03-25 15:08:13 +01:00
Jakub Vrana
4d2b5144b1 Doc-comments: Improve array @var 2025-03-25 14:41:26 +01:00
Jakub Vrana
2ee325183b Doc-comment: Improve array @param
This uses syntax from https://phpstan.org/writing-php-code/phpdoc-types#general-arrays.

int[] means an array of ints with arbitrary keys (usually strings)
list<string> means an array of strings with sequential integer keys starting at 0
list<string>[] means an arbitrary array of string lists
list<string[]> means list of arbitrary string arrays
string[][] means two dimensional array with arbitrary keys in both dimensions
array was left in the comments for https://phpstan.org/writing-php-code/phpdoc-types#array-shapes
2025-03-25 14:31:27 +01:00
Jakub Vrana
26aa48122f Doc-comments: Improve array @return 2025-03-25 13:27:54 +01:00
Jakub Vrana
19b7358452 AdminerSqlGemini: Highlight button 2025-03-25 07:33:30 +01:00
Jakub Vrana
a1080ea8dc JS: Simplify SubmitHighlight 2025-03-25 07:03:42 +01:00
Jakub Vrana
9b1b779dbd AdminerSqlGemini: Handle Ctrl+Enter 2025-03-25 06:56:00 +01:00
Jakub Vrana
4bbbea2fbe AdminerSqlGemini: Wrap returned text to comment 2025-03-25 06:49:34 +01:00
Jakub Vrana
16e49d27cb AdminerSqlGemini: Return more columns by default 2025-03-25 06:29:41 +01:00
Jakub Vrana
190d91a0f9 AdminerSqlGemini: Send vendor 2025-03-25 06:17:27 +01:00
Jakub Vrana
2c72b879e9 Simplify saving flavor 2025-03-25 06:15:09 +01:00
Jakub Vrana
001f5ac21a AdminerSqlGemini: Avoid button jumping 2025-03-25 06:09:00 +01:00
Jakub Vrana
b13c76149f Add comment 2025-03-25 06:08:54 +01:00
Jakub Vrana
27a5aeea86 AdminerSqlLog: Update comment 2025-03-24 23:49:30 +01:00
Jakub Vrana
5a1be8ae65 More developer notes 2025-03-24 19:17:18 +01:00
Jakub Vrana
5dea23a07a Develop 2025-03-24 17:36:47 +01:00
187 changed files with 3617 additions and 3899 deletions

2
.gitignore vendored
View File

@@ -5,6 +5,8 @@
/adminer*.php
/editor*.php
/tests/pdo-*.html
/tests/screenshots/
/tests/cropped/
/vendor/
adminer-plugins/
adminer-plugins.php

View File

@@ -1,13 +0,0 @@
language: php
php:
- 5.6
- 7.1
- 7.2
- 7.3
- 7.4
- 8.0
- 8.1
- 8.2
- 8.3
- 8.4
script: git diff --name-only $TRAVIS_COMMIT_RANGE | grep '\.php$' | xargs -n1 -P8 php -l | grep -v 'No syntax errors'; test $? -eq 1

View File

@@ -1,3 +1,14 @@
## Adminer 5.1.1 (released 2025-04-02)
- Export: Fix tar (regression from 5.0.3)
- Select: Allow ordering by COUNT(*) (bug #966, regression from 5.0.2)
- Optimize retrieving columns for schema
- Elasticsearch: Make it work with Elasticsearch 8
- CSS: Hide menu on mobile
- CSS: Invert icons in dark mode
- Plugins: Allow changing CSP by more plugins
- New plugin: Use Monaco Editor for syntax highlighting
- New plugin: Use Prism for syntax highlighting
## Adminer 5.1.0 (released 2025-03-24)
- Display collation at table structure if different from table
- Ctrl+click in select moves the cursor in modern browsers

View File

@@ -7,10 +7,10 @@
## Features
- **Supports:** MySQL, MariaDB, PostgreSQL, CockroachDB, SQLite, MS SQL, Oracle
- **Plugins for:** Elasticsearch, SimpleDB, MongoDB, Firebird, ClickHouse, IMAP
- **Requirements:** PHP 5.3+
- **Requirements:** PHP 5.3+ (compiled file), PHP 7.4+ (source codes)
## Screenshot
![Screenshot](https://www.adminer.org/static/screenshots/table.png)
![Table structure](https://www.adminer.org/static/screenshots/table.png)
## Installation
If downloaded from Git then run: `git submodule update --init`
@@ -26,25 +26,4 @@ If downloaded from Git then run: `git submodule update --init`
- `tests/*.html` - Katalon Recorder test suites
## Plugins
There are [several plugins](/plugins/) distributed with Adminer, as well as many user-contributed plugins linked on the [Adminer Plugins page](https://www.adminer.org/plugins/).
To use a plugin, simply upload it to the `adminer-plugins/` directory next to `adminer.php`. You can also upload plugins for drivers (e.g., `elastic.php`) in this directory.
```
- adminer.php
- adminer-plugins/
- dump-xml.php
- login-password-less.php
- elastic.php
- ...
- adminer-plugins.php
```
Some plugins require configuration. To use them, create a file named `adminer-plugins.php`. You can also specify the loading order in this file.
```php
<?php // adminer-plugins.php
return array(
new AdminerLoginPasswordLess('$2y$07$Czp9G/aLi3AnaUqpvkF05OHO1LMizrAgMLvnaOdvQovHaRv28XDhG'),
// You can specify all plugins here or just the ones needing configuration.
);
```
There are several plugins distributed with Adminer, as well as many user-contributed plugins listed on the [Adminer Plugins page](https://www.adminer.org/plugins/).

View File

@@ -19,13 +19,14 @@ foreach ($routine["fields"] as $i => $field) {
if (!$error && $_POST) {
$call = array();
foreach ($routine["fields"] as $key => $field) {
$val = "";
if (in_array($key, $in)) {
$val = process_input($field);
if ($val === false) {
$val = "''";
}
if (isset($out[$key])) {
$connection->query("SET @" . idf_escape($field["field"]) . " = $val");
connection()->query("SET @" . idf_escape($field["field"]) . " = $val");
}
}
$call[] = (isset($out[$key]) ? "@" . idf_escape($field["field"]) : $val);
@@ -33,31 +34,31 @@ if (!$error && $_POST) {
$query = (isset($_GET["callf"]) ? "SELECT" : "CALL") . " " . table($PROCEDURE) . "(" . implode(", ", $call) . ")";
$start = microtime(true);
$result = $connection->multi_query($query);
$affected = $connection->affected_rows; // getting warnings overwrites this
echo $adminer->selectQuery($query, $start, !$result);
$result = connection()->multi_query($query);
$affected = connection()->affected_rows; // getting warnings overwrites this
echo adminer()->selectQuery($query, $start, !$result);
if (!$result) {
echo "<p class='error'>" . error() . "\n";
} else {
$connection2 = connect($adminer->credentials());
if (is_object($connection2)) {
$connection2 = connect(adminer()->credentials());
if ($connection2) {
$connection2->select_db(DB);
}
do {
$result = $connection->store_result();
$result = connection()->store_result();
if (is_object($result)) {
select($result, $connection2);
print_select_result($result, $connection2);
} else {
echo "<p class='message'>" . lang('Routine has been called, %d row(s) affected.', $affected)
. " <span class='time'>" . @date("H:i:s") . "</span>\n" // @ - time zone may be not set
;
}
} while ($connection->next_result());
} while (connection()->next_result());
if ($out) {
select($connection->query("SELECT " . implode(", ", $out)));
print_select_result(connection()->query("SELECT " . implode(", ", $out)));
}
}
}
@@ -70,14 +71,14 @@ if ($in) {
foreach ($in as $key) {
$field = $routine["fields"][$key];
$name = $field["field"];
echo "<tr><th>" . $adminer->fieldName($field);
$value = $_POST["fields"][$name];
echo "<tr><th>" . adminer()->fieldName($field);
$value = idx($_POST["fields"], $name);
if ($value != "") {
if ($field["type"] == "set") {
$value = implode(",", $value);
}
}
input($field, $value, (string) $_POST["function"][$name]); // param name can be empty
input($field, $value, idx($_POST["function"], $name, "")); // param name can be empty
echo "\n";
}
echo "</table>\n";
@@ -90,9 +91,13 @@ if ($in) {
<pre>
<?php
function pre_tr($s) {
/** Format string as table row
* @return string HTML
*/
function pre_tr(string $s): string {
return preg_replace('~^~m', '<tr>', preg_replace('~\|~', '<td>', preg_replace('~\|$~m', "", rtrim($s))));
}
$table = '(\+--[-+]+\+\n)';
$row = '(\| .* \|\n)';
echo preg_replace_callback(

View File

@@ -7,7 +7,7 @@ $row = $_POST;
if ($row && !$error) {
if (JUSH == "sqlite") {
$result = recreate_table($TABLE, $TABLE, array(), array(), array(), 0, array(), $name, ($row["drop"] ? "" : $row["clause"]));
$result = recreate_table($TABLE, $TABLE, array(), array(), array(), "", array(), "$name", ($row["drop"] ? "" : $row["clause"]));
} else {
$result = ($name == "" || queries("ALTER TABLE " . table($TABLE) . " DROP CONSTRAINT " . idf_escape($name)));
if (!$row["drop"]) {
@@ -24,7 +24,7 @@ if ($row && !$error) {
page_header(($name != "" ? lang('Alter check') . ": " . h($name) : lang('Create check')), $error, array("table" => $TABLE));
if (!$row) {
$checks = $driver->checkConstraints($TABLE);
$checks = driver()->checkConstraints($TABLE);
$row = array("name" => $name, "clause" => $checks[$name]);
}
?>

View File

@@ -17,8 +17,8 @@ $orig_fields = array();
$table_status = array();
if ($TABLE != "") {
$orig_fields = fields($TABLE);
$table_status = table_status($TABLE);
if (!$table_status) {
$table_status = table_status1($TABLE);
if (count($table_status) < 2) { // there's only the Name field
$error = lang('No tables.');
}
}
@@ -140,7 +140,7 @@ if ($_POST && !process_fields($row["fields"]) && !$error) {
page_header(($TABLE != "" ? lang('Alter table') : lang('Create table')), $error, array("table" => $TABLE), h($TABLE));
if (!$_POST) {
$types = $driver->types();
$types = driver()->types();
$row = array(
"Engine" => $_COOKIE["adminer_engine"],
"fields" => array(array("field" => "", "type" => (isset($types["int"]) ? "int" : (isset($types["integer"]) ? "integer" : "")), "on_update" => "")),
@@ -168,7 +168,10 @@ if (!$_POST) {
}
$collations = collations();
$engines = $driver->engines();
if (is_array(reset($collations))) {
$collations = call_user_func_array('array_merge', array_values($collations));
}
$engines = driver()->engines();
// case of engine may differ
foreach ($engines as $engine) {
if (!strcasecmp($engine, $row["Engine"])) {
@@ -185,7 +188,7 @@ if (support("columns") || $TABLE == "") {
echo lang('Table name') . ": <input name='name'" . ($TABLE == "" && !$_POST ? " autofocus" : "") . " data-maxlength='64' value='" . h($row["name"]) . "' autocapitalize='off'>\n";
echo ($engines ? html_select("Engine", array("" => "(" . lang('engine') . ")") + $engines, $row["Engine"]) . on_help("event.target.value", 1) . script("qsl('select').onchange = helpClose;") . "\n" : "");
if ($collations) {
echo "<datalist id='collations'>" . optionlist($collations) . "</datalist>";
echo "<datalist id='collations'>" . optionlist($collations) . "</datalist>\n";
echo (preg_match("~sqlite|mssql~", JUSH) ? "" : "<input list='collations' name='Collation' value='" . h($row["Collation"]) . "' placeholder='(" . lang('collation') . ")'>");
}
echo "<input type='submit' value='" . lang('Save') . "'>\n";
@@ -230,7 +233,7 @@ if (support("partitioning")) {
echo '<tr>';
echo '<td><input name="partition_names[]" value="' . h($val) . '" autocapitalize="off">';
echo ($key == count($row["partition_names"]) - 1 ? script("qsl('input').oninput = partitionNameChange;") : '');
echo '<td><input name="partition_values[]" value="' . h($row["partition_values"][$key]) . '">';
echo '<td><input name="partition_values[]" value="' . h(idx($row["partition_values"], $key)) . '">';
}
echo "</table>\n</div></fieldset>\n";
}

View File

@@ -3,7 +3,7 @@ namespace Adminer;
$row = $_POST;
if ($_POST && !$error && !isset($_POST["add_x"])) { // add is an image and PHP changes add.x to add_x
if ($_POST && !$error && !$_POST["add"]) {
$name = trim($row["name"]);
if ($_POST["drop"]) {
$_GET["db"] = ""; // to save in global history
@@ -60,7 +60,7 @@ if ($_POST) {
<form action="" method="post">
<p>
<?php
echo ($_POST["add_x"] || strpos($name, "\n")
echo ($_POST["add"] || strpos($name, "\n")
? '<textarea autofocus name="name" rows="10" cols="40">' . h($name) . '</textarea><br>'
: '<input name="name" autofocus value="' . h($name) . '" data-maxlength="64" autocapitalize="off">'
) . "\n" . ($collations ? html_select("collation", array("" => "(" . lang('collation') . ")") + $collations, $row["collation"]) . doc_link(array(
@@ -73,8 +73,8 @@ echo ($_POST["add_x"] || strpos($name, "\n")
<?php
if (DB != "") {
echo "<input type='submit' name='drop' value='" . lang('Drop') . "'>" . confirm(lang('Drop %s?', DB)) . "\n";
} elseif (!$_POST["add_x"] && $_GET["db"] == "") {
echo "<input type='image' class='icon' name='add' src='../adminer/static/plus.gif' alt='+' title='" . lang('Add next') . "'>\n";
} elseif (!$_POST["add"] && $_GET["db"] == "") {
echo icon("plus", "add[0]", "+", lang('Add next')) . "\n";
}
echo input_token();
?>

View File

@@ -54,7 +54,7 @@ if ($tables_views && !$error && !$_POST["search"]) {
page_header(($_GET["ns"] == "" ? lang('Database') . ": " . h(DB) : lang('Schema') . ": " . h($_GET["ns"])), $error, true);
if ($adminer->homepage()) {
if (adminer()->homepage()) {
if ($_GET["ns"] !== "") {
echo "<h3 id='tables-views'>" . lang('Tables and views') . "</h3>\n";
$tables_list = tables_list();
@@ -69,7 +69,7 @@ if ($adminer->homepage()) {
echo " <input type='submit' name='search' value='" . lang('Search') . "'>\n";
echo "</div></fieldset>\n";
if ($_POST["search"] && $_POST["query"] != "") {
$_GET["where"][0]["op"] = $driver->convertOperator("LIKE %%");
$_GET["where"][0]["op"] = driver()->convertOperator("LIKE %%");
search_tables();
}
}
@@ -93,7 +93,7 @@ if ($adminer->homepage()) {
foreach ($tables_list as $name => $type) {
$view = ($type !== null && !preg_match('~table|sequence~i', $type));
$id = h("Table-" . $name);
echo '<tr><td>' . checkbox(($view ? "views[]" : "tables[]"), $name, in_array($name, $tables_views, true), "", "", "", $id);
echo '<tr><td>' . checkbox(($view ? "views[]" : "tables[]"), $name, in_array("$name", $tables_views, true), "", "", "", $id); // "$name" to check numeric table names
echo '<th>' . (support("table") || support("indexes") ? "<a href='" . h(ME) . "table=" . urlencode($name) . "' title='" . lang('Show structure') . "' id='$id'>" . h($name) . '</a>' : h($name));
if ($view) {
echo '<td colspan="6"><a href="' . h(ME) . "view=" . urlencode($name) . '" title="' . lang('Alter view') . '">' . (preg_match('~materialized~i', $type) ? lang('Materialized view') : lang('View')) . '</a>';
@@ -146,7 +146,7 @@ if ($adminer->homepage()) {
: "")))
. "<input type='submit' name='truncate' value='" . lang('Truncate') . "'> " . on_help(JUSH == "sqlite" ? "'DELETE'" : "'TRUNCATE" . (JUSH == "pgsql" ? "'" : " TABLE'")) . confirm()
. "<input type='submit' name='drop' value='" . lang('Drop') . "'>" . on_help("'DROP TABLE'") . confirm() . "\n";
$databases = (support("scheme") ? $adminer->schemas() : $adminer->databases());
$databases = (support("scheme") ? adminer()->schemas() : adminer()->databases());
if (count($databases) != 1 && JUSH != "sqlite") {
$db = (isset($_POST["target"]) ? $_POST["target"] : (support("scheme") ? $_GET["ns"] : DB));
echo "<p>" . lang('Move to other database') . ": ";

View File

@@ -1,17 +1,11 @@
<?php
function adminer_object() {
include_once "../plugins/plugin.php";
include_once "../plugins/designs.php";
$designs = array();
foreach (glob("../designs/*", GLOB_ONLYDIR) as $dirname) {
foreach (array("", "-dark") as $mode) {
$filename = "$dirname/adminer$mode.css";
if (file_exists($filename)) {
$designs[$filename] = basename($dirname);
}
}
foreach (glob("../designs/*/*.css") as $filename) {
$designs[$filename] = basename(dirname($filename));
}
return new AdminerPlugin(array(
return new Adminer\Plugins(array(
new AdminerDesigns($designs),
));
}

View File

@@ -6,7 +6,7 @@ $fields = fields($TABLE);
header("Content-Type: application/octet-stream");
header("Content-Disposition: attachment; filename=" . friendly_url("$TABLE-" . implode("_", $_GET["where"])) . "." . friendly_url($_GET["field"]));
$select = array(idf_escape($_GET["field"]));
$result = $driver->select($TABLE, $select, array(where($_GET, $fields)), $select);
$result = driver()->select($TABLE, $select, array(where($_GET, $fields)), $select);
$row = ($result ? $result->fetch_row() : array());
echo $driver->value($row[0], $fields[$_GET["field"]]);
echo driver()->value($row[0], $fields[$_GET["field"]]);
exit; // don't output footer

View File

@@ -7,13 +7,13 @@
namespace Adminer;
$drivers["mssql"] = "MS SQL";
add_driver("mssql", "MS SQL");
if (isset($_GET["mssql"])) {
define('Adminer\DRIVER', "mssql");
if (extension_loaded("sqlsrv") && $_GET["ext"] != "pdo") {
class Db {
public $extension = "sqlsrv", $flavor = '', $server_info, $affected_rows, $errno, $error;
class Db extends SqlDb {
public string $extension = "sqlsrv";
private $link, $result;
private function get_error() {
@@ -25,17 +25,16 @@ if (isset($_GET["mssql"])) {
$this->error = rtrim($this->error);
}
function connect($server, $username, $password) {
global $adminer;
function attach(?string $server, string $username, string $password): string {
$connection_info = array("UID" => $username, "PWD" => $password, "CharacterSet" => "UTF-8");
$ssl = $adminer->connectSsl();
$ssl = adminer()->connectSsl();
if (isset($ssl["Encrypt"])) {
$connection_info["Encrypt"] = $ssl["Encrypt"];
}
if (isset($ssl["TrustServerCertificate"])) {
$connection_info["TrustServerCertificate"] = $ssl["TrustServerCertificate"];
}
$db = $adminer->database();
$db = adminer()->database();
if ($db != "") {
$connection_info["Database"] = $db;
}
@@ -46,19 +45,19 @@ if (isset($_GET["mssql"])) {
} else {
$this->get_error();
}
return (bool) $this->link;
return ($this->link ? '' : $this->error);
}
function quote($string) {
function quote(string $string): string {
$unicode = strlen($string) != strlen(utf8_decode($string));
return ($unicode ? "N" : "") . "'" . str_replace("'", "''", $string) . "'";
}
function select_db($database) {
function select_db(string $database) {
return $this->query(use_sql($database));
}
function query($query, $unbuffered = false) {
function query(string $query, bool $unbuffered = false) {
$result = sqlsrv_query($this->link, $query); //! , array(), ($unbuffered ? array() : array("Scrollable" => "keyset"))
$this->error = "";
if (!$result) {
@@ -68,7 +67,7 @@ if (isset($_GET["mssql"])) {
return $this->store_result($result);
}
function multi_query($query) {
function multi_query(string $query) {
$this->result = sqlsrv_query($this->link, $query);
$this->error = "";
if (!$this->result) {
@@ -92,17 +91,8 @@ if (isset($_GET["mssql"])) {
return true;
}
function next_result() {
return $this->result ? sqlsrv_next_result($this->result) : null;
}
function result($query, $field = 0) {
$result = $this->query($query);
if (!is_object($result)) {
return false;
}
$row = $result->fetch_row();
return $row[$field];
function next_result(): bool {
return $this->result ? !!sqlsrv_next_result($this->result) : false;
}
}
@@ -133,7 +123,7 @@ if (isset($_GET["mssql"])) {
return $this->convert(sqlsrv_fetch_array($this->result, SQLSRV_FETCH_NUMERIC));
}
function fetch_field() {
function fetch_field(): \stdClass {
if (!$this->fields) {
$this->fields = sqlsrv_field_metadata($this->result);
}
@@ -168,8 +158,8 @@ if (isset($_GET["mssql"])) {
}
} else {
class MssqlDb extends PdoDb {
function select_db($database) {
abstract class MssqlDb extends PdoDb {
function select_db(string $database) {
// database selection is separated from the connection so dbname in DSN can't be used
return $this->query(use_sql($database));
}
@@ -180,8 +170,7 @@ if (isset($_GET["mssql"])) {
}
function last_id($result) {
global $connection;
return $connection->lastInsertId();
return connection()->lastInsertId();
}
function explain($connection, $query) {
@@ -189,21 +178,19 @@ if (isset($_GET["mssql"])) {
if (extension_loaded("pdo_sqlsrv")) {
class Db extends MssqlDb {
public $extension = "PDO_SQLSRV";
public string $extension = "PDO_SQLSRV";
function connect($server, $username, $password) {
$this->dsn("sqlsrv:Server=" . str_replace(":", ",", $server), $username, $password);
return true;
function attach(?string $server, string $username, string $password): string {
return $this->dsn("sqlsrv:Server=" . str_replace(":", ",", $server), $username, $password);
}
}
} elseif (extension_loaded("pdo_dblib")) {
class Db extends MssqlDb {
public $extension = "PDO_DBLIB";
public string $extension = "PDO_DBLIB";
function connect($server, $username, $password) {
$this->dsn("dblib:charset=utf8;host=" . str_replace(":", ";unix_socket=", preg_replace('~:(\d)~', ';port=\1', $server)), $username, $password);
return true;
function attach(?string $server, string $username, string $password): string {
return $this->dsn("dblib:charset=utf8;host=" . str_replace(":", ";unix_socket=", preg_replace('~:(\d)~', ';port=\1', $server)), $username, $password);
}
}
}
@@ -211,25 +198,29 @@ if (isset($_GET["mssql"])) {
class Driver extends SqlDriver {
static $possibleDrivers = array("SQLSRV", "PDO_SQLSRV", "PDO_DBLIB");
static $jush = "mssql";
static array $extensions = array("SQLSRV", "PDO_SQLSRV", "PDO_DBLIB");
static string $jush = "mssql";
public $editFunctions = array(
array(
"date|time" => "getdate",
), array(
"int|decimal|real|float|money|datetime" => "+/-",
"char|text" => "+",
)
public array $insertFunctions = array("date|time" => "getdate");
public array $editFunctions = array(
"int|decimal|real|float|money|datetime" => "+/-",
"char|text" => "+",
);
public $operators = array("=", "<", ">", "<=", ">=", "!=", "LIKE", "LIKE %%", "IN", "IS NULL", "NOT LIKE", "NOT IN", "IS NOT NULL");
public $functions = array("len", "lower", "round", "upper");
public $grouping = array("avg", "count", "count distinct", "max", "min", "sum");
public $onActions = "NO ACTION|CASCADE|SET NULL|SET DEFAULT";
public $generated = array("PERSISTED", "VIRTUAL");
public array $operators = array("=", "<", ">", "<=", ">=", "!=", "LIKE", "LIKE %%", "IN", "IS NULL", "NOT LIKE", "NOT IN", "IS NOT NULL");
public array $functions = array("len", "lower", "round", "upper");
public array $grouping = array("avg", "count", "count distinct", "max", "min", "sum");
public array $generated = array("PERSISTED", "VIRTUAL");
public string $onActions = "NO ACTION|CASCADE|SET NULL|SET DEFAULT";
function __construct($connection) {
static function connect(?string $server, string $username, string $password) {
if ($server == "") {
$server = "localhost:1433";
}
return parent::connect($server, $username, $password);
}
function __construct(Db $connection) {
parent::__construct($connection);
$this->types = array( //! use sys.types
lang('Numbers') => array("tinyint" => 3, "smallint" => 5, "int" => 10, "bigint" => 20, "bit" => 1, "decimal" => 0, "real" => 12, "float" => 53, "smallmoney" => 10, "money" => 20),
@@ -239,7 +230,7 @@ if (isset($_GET["mssql"])) {
);
}
function insertUpdate($table, $rows, $primary) {
function insertUpdate(string $table, array $rows, array $primary) {
$fields = fields($table);
$update = array();
$where = array();
@@ -283,7 +274,7 @@ if (isset($_GET["mssql"])) {
return queries("BEGIN TRANSACTION");
}
function tableHelp($name, $is_view = false) {
function tableHelp(string $name, bool $is_view = false) {
$links = array(
"sys" => "catalog-views/sys-",
"INFORMATION_SCHEMA" => "information-schema-views/",
@@ -305,23 +296,12 @@ if (isset($_GET["mssql"])) {
return ($_GET["ns"] != "" ? idf_escape($_GET["ns"]) . "." : "") . idf_escape($idf);
}
function connect($credentials) {
$connection = new Db;
if ($credentials[0] == "") {
$credentials[0] = "localhost:1433";
}
if ($connection->connect($credentials[0], $credentials[1], $credentials[2])) {
return $connection;
}
return $connection->error;
}
function get_databases() {
function get_databases($flush) {
return get_vals("SELECT name FROM sys.databases WHERE name NOT IN ('master', 'tempdb', 'model', 'msdb')");
}
function limit($query, $where, $limit, $offset = 0, $separator = " ") {
return ($limit !== null ? " TOP (" . ($limit + $offset) . ")" : "") . " $query$where"; // seek later
return ($limit ? " TOP (" . ($limit + $offset) . ")" : "") . " $query$where"; // seek later
}
function limit1($table, $query, $where, $separator = "\n") {
@@ -341,10 +321,9 @@ if (isset($_GET["mssql"])) {
}
function count_tables($databases) {
global $connection;
$return = array();
foreach ($databases as $db) {
$connection->select_db($db);
connection()->select_db($db);
$return[$db] = get_val("SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES");
}
return $return;
@@ -357,9 +336,6 @@ if (isset($_GET["mssql"])) {
FROM sys.all_objects AS ao
WHERE schema_id = SCHEMA_ID(" . q(get_schema()) . ") AND type IN ('S', 'U', 'V') " . ($name != "" ? "AND name = " . q($name) : "ORDER BY name")) as $row
) {
if ($name != "") {
return $row;
}
$return[$row["Name"]] = $row;
}
return $return;
@@ -388,7 +364,7 @@ WHERE c.object_id = " . q($table_id)) as $row
) {
$type = $row["type"];
$length = (preg_match("~char|binary~", $type)
? $row["max_length"] / ($type[0] == 'n' ? 2 : 1)
? intval($row["max_length"]) / ($type[0] == 'n' ? 2 : 1)
: ($type == "decimal" ? "$row[precision],$row[scale]" : "")
);
$return[$row["name"]] = array(
@@ -449,8 +425,7 @@ WHERE OBJECT_NAME(i.object_id) = " . q($table), $connection2) as $row
}
function error() {
global $connection;
return nl_br(h(preg_replace('~^(\[[^]]*])+~m', '', $connection->error)));
return nl_br(h(preg_replace('~^(\[[^]]*])+~m', '', connection()->error)));
}
function create_database($db, $collation) {
@@ -601,7 +576,7 @@ WHERE OBJECT_NAME(i.object_id) = " . q($table), $connection2) as $row
return apply_queries("ALTER SCHEMA " . idf_escape($target) . " TRANSFER", array_merge($tables, $views));
}
function trigger($name) {
function trigger($name, $table) {
if ($name == "") {
return array();
}
@@ -661,8 +636,7 @@ WHERE sys1.xtype = 'TR' AND sys2.name = " . q($table)) as $row
}
function create_sql($table, $auto_increment, $style) {
global $driver;
if (is_view(table_status($table))) {
if (is_view(table_status1($table))) {
$view = view($table);
return "CREATE VIEW " . table($table) . " AS $view[select]";
}
@@ -685,7 +659,7 @@ WHERE sys1.xtype = 'TR' AND sys2.name = " . q($table)) as $row
$fields[] = ($index["type"] == "INDEX" ? "INDEX $name" : "CONSTRAINT $name " . ($index["type"] == "UNIQUE" ? "UNIQUE" : "PRIMARY KEY")) . " (" . implode(", ", $columns) . ")";
}
}
foreach ($driver->checkConstraints($table) as $name => $check) {
foreach (driver()->checkConstraints($table) as $name => $check) {
$fields[] = "CONSTRAINT " . idf_escape($name) . " CHECK ($check)";
}
return "CREATE TABLE " . table($table) . " (\n\t" . implode(",\n\t", $fields) . "\n)";
@@ -710,7 +684,7 @@ WHERE sys1.xtype = 'TR' AND sys2.name = " . q($table)) as $row
function trigger_sql($table) {
$return = "";
foreach (triggers($table) as $name => $trigger) {
$return .= create_trigger(" ON " . table($table), trigger($name)) . ";";
$return .= create_trigger(" ON " . table($table), trigger($name, $table)) . ";";
}
return $return;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,15 +1,15 @@
<?php
namespace Adminer;
$drivers["oracle"] = "Oracle (beta)";
add_driver("oracle", "Oracle (beta)");
if (isset($_GET["oracle"])) {
define('Adminer\DRIVER', "oracle");
if (extension_loaded("oci8") && $_GET["ext"] != "pdo") {
class Db {
public $extension = "oci8", $flavor = '', $server_info, $affected_rows, $errno, $error;
class Db extends SqlDb {
public string $extension = "oci8";
public $_current_db;
private $link, $result;
private $link;
function _error($errno, $error) {
if (ini_bool("html_errors")) {
@@ -19,27 +19,26 @@ if (isset($_GET["oracle"])) {
$this->error = $error;
}
function connect($server, $username, $password) {
function attach(?string $server, string $username, string $password): string {
$this->link = @oci_new_connect($username, $password, $server, "AL32UTF8");
if ($this->link) {
$this->server_info = oci_server_version($this->link);
return true;
return '';
}
$error = oci_error();
$this->error = $error["message"];
return false;
return $error["message"];
}
function quote($string) {
function quote(string $string): string {
return "'" . str_replace("'", "''", $string) . "'";
}
function select_db($database) {
function select_db(string $database) {
$this->_current_db = $database;
return true;
}
function query($query, $unbuffered = false) {
function query(string $query, bool $unbuffered = false) {
$result = oci_parse($this->link, $query);
$this->error = "";
if (!$result) {
@@ -60,23 +59,6 @@ if (isset($_GET["oracle"])) {
}
return $return;
}
function multi_query($query) {
return $this->result = $this->query($query);
}
function store_result() {
return $this->result;
}
function next_result() {
return false;
}
function result($query, $field = 0) {
$result = $this->query($query);
return (is_object($result) ? $result->fetch_column($field) : false);
}
}
class Result {
@@ -89,7 +71,7 @@ if (isset($_GET["oracle"])) {
private function convert($row) {
foreach ((array) $row as $key => $val) {
if (is_a($val, 'OCI-Lob')) {
if (is_a($val, 'OCILob') || is_a($val, 'OCI-Lob')) {
$row[$key] = $val->load();
}
}
@@ -104,11 +86,7 @@ if (isset($_GET["oracle"])) {
return $this->convert(oci_fetch_row($this->result));
}
function fetch_column($field) {
return (oci_fetch($this->result) ? oci_result($this->result, $field + 1) : false);
}
function fetch_field() {
function fetch_field(): \stdClass {
$column = $this->offset++;
$return = new \stdClass;
$return->name = oci_field_name($this->result, $column);
@@ -124,15 +102,14 @@ if (isset($_GET["oracle"])) {
} elseif (extension_loaded("pdo_oci")) {
class Db extends PdoDb {
public $extension = "PDO_OCI";
public string $extension = "PDO_OCI";
public $_current_db;
function connect($server, $username, $password) {
$this->dsn("oci:dbname=//$server;charset=AL32UTF8", $username, $password);
return true;
function attach(?string $server, string $username, string $password): string {
return $this->dsn("oci:dbname=//$server;charset=AL32UTF8", $username, $password);
}
function select_db($database) {
function select_db(string $database) {
$this->_current_db = $database;
return true;
}
@@ -143,25 +120,24 @@ if (isset($_GET["oracle"])) {
class Driver extends SqlDriver {
static $possibleDrivers = array("OCI8", "PDO_OCI");
static $jush = "oracle";
static array $extensions = array("OCI8", "PDO_OCI");
static string $jush = "oracle";
public $editFunctions = array(
array( //! no parentheses
"date" => "current_date",
"timestamp" => "current_timestamp",
), array(
"number|float|double" => "+/-",
"date|timestamp" => "+ interval/- interval",
"char|clob" => "||",
)
public array $insertFunctions = array( //! no parentheses
"date" => "current_date",
"timestamp" => "current_timestamp",
);
public array $editFunctions = array(
"number|float|double" => "+/-",
"date|timestamp" => "+ interval/- interval",
"char|clob" => "||",
);
public $operators = array("=", "<", ">", "<=", ">=", "!=", "LIKE", "LIKE %%", "IN", "IS NULL", "NOT LIKE", "NOT IN", "IS NOT NULL", "SQL");
public $functions = array("length", "lower", "round", "upper");
public $grouping = array("avg", "count", "count distinct", "max", "min", "sum");
public array $operators = array("=", "<", ">", "<=", ">=", "!=", "LIKE", "LIKE %%", "IN", "IS NULL", "NOT LIKE", "NOT IN", "IS NOT NULL", "SQL");
public array $functions = array("length", "lower", "round", "upper");
public array $grouping = array("avg", "count", "count distinct", "max", "min", "sum");
function __construct($connection) {
function __construct(Db $connection) {
parent::__construct($connection);
$this->types = array(
lang('Numbers') => array("number" => 38, "binary_float" => 12, "binary_double" => 21),
@@ -177,8 +153,7 @@ if (isset($_GET["oracle"])) {
return true; // automatic start
}
function insertUpdate($table, $rows, $primary) {
global $connection;
function insertUpdate(string $table, array $rows, array $primary) {
foreach ($rows as $set) {
$update = array();
$where = array();
@@ -189,7 +164,7 @@ if (isset($_GET["oracle"])) {
}
}
if (
!(($where && queries("UPDATE " . table($table) . " SET " . implode(", ", $update) . " WHERE " . implode(" AND ", $where)) && $connection->affected_rows)
!(($where && queries("UPDATE " . table($table) . " SET " . implode(", ", $update) . " WHERE " . implode(" AND ", $where)) && connection()->affected_rows)
|| queries("INSERT INTO " . table($table) . " (" . implode(", ", array_keys($set)) . ") VALUES (" . implode(", ", $set) . ")"))
) {
return false;
@@ -198,7 +173,7 @@ if (isset($_GET["oracle"])) {
return true;
}
function hasCStyleEscapes() {
function hasCStyleEscapes(): bool {
return true;
}
}
@@ -213,15 +188,7 @@ if (isset($_GET["oracle"])) {
return idf_escape($idf);
}
function connect($credentials) {
$connection = new Db;
if ($connection->connect($credentials[0], $credentials[1], $credentials[2])) {
return $connection;
}
return $connection->error;
}
function get_databases() {
function get_databases($flush) {
return get_vals(
"SELECT DISTINCT tablespace_name FROM (
SELECT tablespace_name FROM user_tablespaces
@@ -233,7 +200,7 @@ ORDER BY 1"
function limit($query, $where, $limit, $offset = 0, $separator = " ") {
return ($offset ? " * FROM (SELECT t.*, rownum AS rnum FROM (SELECT $query$where) t WHERE rownum <= " . ($limit + $offset) . ") WHERE rnum > $offset"
: ($limit !== null ? " * FROM (SELECT $query$where) WHERE rownum <= " . ($limit + $offset)
: ($limit ? " * FROM (SELECT $query$where) WHERE rownum <= " . ($limit + $offset)
: " $query$where"
));
}
@@ -251,9 +218,8 @@ ORDER BY 1"
}
function get_current_db() {
global $connection;
$db = $connection->_current_db ?: DB;
unset($connection->_current_db);
$db = connection()->_current_db ?: DB;
unset(connection()->_current_db);
return $db;
}
@@ -298,9 +264,6 @@ ORDER BY 1"
UNION SELECT view_name, 'view', 0, 0 FROM $view" . ($name != "" ? " WHERE view_name = $search" : "") . "
ORDER BY 1") as $row
) {
if ($name != "") {
return $row;
}
$return[$row["Name"]] = $row;
}
return $return;
@@ -377,8 +340,7 @@ ORDER BY ac.constraint_type, aic.column_position", $connection2) as $row
}
function error() {
global $connection;
return h($connection->error); //! highlight sqltext from offset
return h(connection()->error); //! highlight sqltext from offset
}
function explain($connection, $query) {
@@ -504,9 +466,8 @@ AND c_src.TABLE_NAME = " . q($table);
}
function set_schema($scheme, $connection2 = null) {
global $connection;
if (!$connection2) {
$connection2 = $connection;
$connection2 = connection();
}
return $connection2->query("ALTER SESSION SET CURRENT_SCHEMA = " . idf_escape($scheme));
}

View File

@@ -1,14 +1,15 @@
<?php
namespace Adminer;
$drivers["pgsql"] = "PostgreSQL";
add_driver("pgsql", "PostgreSQL");
if (isset($_GET["pgsql"])) {
define('Adminer\DRIVER', "pgsql");
if (extension_loaded("pgsql") && $_GET["ext"] != "pdo") {
class Db {
public $extension = "PgSQL", $flavor = '', $server_info, $affected_rows, $error, $timeout;
private $link, $result, $string, $database = true;
class Db extends SqlDb {
public string $extension = "PgSQL";
public int $timeout = 0;
private $link, $string, $database = true;
function _error($errno, $error) {
if (ini_bool("html_errors")) {
@@ -18,12 +19,11 @@ if (isset($_GET["pgsql"])) {
$this->error = $error;
}
function connect($server, $username, $password) {
global $adminer;
$db = $adminer->database();
function attach(?string $server, string $username, string $password): string {
$db = adminer()->database();
set_error_handler(array($this, '_error'));
$this->string = "host='" . str_replace(":", "' port='", addcslashes($server, "'\\")) . "' user='" . addcslashes($username, "'\\") . "' password='" . addcslashes($password, "'\\") . "'";
$ssl = $adminer->connectSsl();
$ssl = adminer()->connectSsl();
if (isset($ssl["mode"])) {
$this->string .= " sslmode='" . $ssl["mode"] . "'";
}
@@ -37,23 +37,22 @@ if (isset($_GET["pgsql"])) {
if ($this->link) {
pg_set_client_encoding($this->link, "UTF8");
}
return (bool) $this->link;
return ($this->link ? '' : $this->error);
}
function quote($string) {
function quote(string $string): string {
return (function_exists('pg_escape_literal')
? pg_escape_literal($this->link, $string) // available since PHP 5.4.4
: "'" . pg_escape_string($this->link, $string) . "'"
);
}
function value($val, $field) {
function value(?string $val, array $field): ?string {
return ($field["type"] == "bytea" && $val !== null ? pg_unescape_bytea($val) : $val);
}
function select_db($database) {
global $adminer;
if ($database == $adminer->database()) {
function select_db(string $database) {
if ($database == adminer()->database()) {
return $this->database;
}
$return = @pg_connect("$this->string dbname='" . addcslashes($database, "'\\") . "'", PGSQL_CONNECT_FORCE_NEW);
@@ -67,7 +66,7 @@ if (isset($_GET["pgsql"])) {
$this->link = @pg_connect("$this->string dbname='postgres'");
}
function query($query, $unbuffered = false) {
function query(string $query, bool $unbuffered = false) {
$result = @pg_query($this->link, $query);
$this->error = "";
if (!$result) {
@@ -86,24 +85,6 @@ if (isset($_GET["pgsql"])) {
return $return;
}
function multi_query($query) {
return $this->result = $this->query($query);
}
function store_result() {
return $this->result;
}
function next_result() {
// PgSQL extension doesn't support multiple results
return false;
}
function result($query, $field = 0) {
$result = $this->query($query);
return ($result ? $result->fetch_column($field) : false);
}
function warnings() {
return h(pg_last_notice($this->link)); // second parameter is available since PHP 7.1.0
}
@@ -126,11 +107,7 @@ if (isset($_GET["pgsql"])) {
return pg_fetch_row($this->result);
}
function fetch_column($field) {
return ($this->num_rows ? pg_fetch_result($this->result, 0, $field) : false);
}
function fetch_field() {
function fetch_field(): \stdClass {
$column = $this->offset++;
$return = new \stdClass;
$return->orgtable = pg_field_table($this->result, $column);
@@ -147,27 +124,25 @@ if (isset($_GET["pgsql"])) {
} elseif (extension_loaded("pdo_pgsql")) {
class Db extends PdoDb {
public $extension = "PDO_PgSQL", $timeout;
public string $extension = "PDO_PgSQL";
public int $timeout = 0;
function connect($server, $username, $password) {
global $adminer;
$db = $adminer->database();
function attach(?string $server, string $username, string $password): string {
$db = adminer()->database();
//! client_encoding is supported since 9.1, but we can't yet use min_version here
$dsn = "pgsql:host='" . str_replace(":", "' port='", addcslashes($server, "'\\")) . "' client_encoding=utf8 dbname='" . ($db != "" ? addcslashes($db, "'\\") : "postgres") . "'";
$ssl = $adminer->connectSsl();
$ssl = adminer()->connectSsl();
if (isset($ssl["mode"])) {
$dsn .= " sslmode='" . $ssl["mode"] . "'";
}
$this->dsn($dsn, $username, $password);
return true;
return $this->dsn($dsn, $username, $password);
}
function select_db($database) {
global $adminer;
return ($adminer->database() == $database);
function select_db(string $database) {
return (adminer()->database() == $database);
}
function query($query, $unbuffered = false) {
function query(string $query, bool $unbuffered = false) {
$return = parent::query($query, $unbuffered);
if ($this->timeout) {
$this->timeout = 0;
@@ -177,7 +152,7 @@ if (isset($_GET["pgsql"])) {
}
function warnings() {
return ''; // not implemented in PDO_PgSQL as of PHP 7.2.1
// not implemented in PDO_PgSQL as of PHP 7.2.1
}
function close() {
@@ -189,14 +164,31 @@ if (isset($_GET["pgsql"])) {
class Driver extends SqlDriver {
static $possibleDrivers = array("PgSQL", "PDO_PgSQL");
static $jush = "pgsql";
static array $extensions = array("PgSQL", "PDO_PgSQL");
static string $jush = "pgsql";
public $operators = array("=", "<", ">", "<=", ">=", "!=", "~", "!~", "LIKE", "LIKE %%", "ILIKE", "ILIKE %%", "IN", "IS NULL", "NOT LIKE", "NOT IN", "IS NOT NULL"); // no "SQL" to avoid CSRF
public $functions = array("char_length", "lower", "round", "to_hex", "to_timestamp", "upper");
public $grouping = array("avg", "count", "count distinct", "max", "min", "sum");
public array $operators = array("=", "<", ">", "<=", ">=", "!=", "~", "!~", "LIKE", "LIKE %%", "ILIKE", "ILIKE %%", "IN", "IS NULL", "NOT LIKE", "NOT IN", "IS NOT NULL"); // no "SQL" to avoid CSRF
public array $functions = array("char_length", "lower", "round", "to_hex", "to_timestamp", "upper");
public array $grouping = array("avg", "count", "count distinct", "max", "min", "sum");
function __construct($connection) {
static function connect(?string $server, string $username, string $password) {
$connection = parent::connect($server, $username, $password);
if (is_string($connection)) {
return $connection;
}
$version = get_val("SELECT version()", 0, $connection);
$connection->flavor = (preg_match('~CockroachDB~', $version) ? 'cockroach' : '');
$connection->server_info = preg_replace('~^\D*([\d.]+[-\w]*).*~', '\1', $version);
if (min_version(9, 0, $connection)) {
$connection->query("SET application_name = 'Adminer'");
}
if ($connection->flavor == 'cockroach') { // we don't use "PostgreSQL / CockroachDB" by default because it's too long
add_driver(DRIVER, "CockroachDB");
}
return $connection;
}
function __construct(Db $connection) {
parent::__construct($connection);
$this->types = array( //! arrays
lang('Numbers') => array("smallint" => 5, "integer" => 10, "bigint" => 19, "boolean" => 1, "numeric" => 0, "real" => 7, "double precision" => 16, "money" => 20),
@@ -212,22 +204,21 @@ if (isset($_GET["pgsql"])) {
$this->types[lang('Strings')]["jsonb"] = 4294967295;
}
}
$this->insertFunctions = array(
"char" => "md5",
"date|time" => "now",
);
$this->editFunctions = array(
array(
"char" => "md5",
"date|time" => "now",
), array(
number_type() => "+/-",
"date|time" => "+ interval/- interval", //! escape
"char|text" => "||",
)
number_type() => "+/-",
"date|time" => "+ interval/- interval", //! escape
"char|text" => "||",
);
if (min_version(12, 0, $connection)) {
$this->generated = array("STORED");
}
}
function enumLength($field) {
function enumLength(array $field) {
$enum = $this->types[lang('User types')][$field["type"]];
return ($enum ? type_values($enum) : "");
}
@@ -236,15 +227,14 @@ if (isset($_GET["pgsql"])) {
$this->types[lang('User types')] = array_flip($types);
}
function insertReturning($table) {
function insertReturning(string $table): string {
$auto_increment = array_filter(fields($table), function ($field) {
return $field['auto_increment'];
});
return (count($auto_increment) == 1 ? " RETURNING " . idf_escape(key($auto_increment)) : "");
}
function insertUpdate($table, $rows, $primary) {
global $connection;
function insertUpdate(string $table, array $rows, array $primary) {
foreach ($rows as $set) {
$update = array();
$where = array();
@@ -255,7 +245,7 @@ if (isset($_GET["pgsql"])) {
}
}
if (
!(($where && queries("UPDATE " . table($table) . " SET " . implode(", ", $update) . " WHERE " . implode(" AND ", $where)) && $connection->affected_rows)
!(($where && queries("UPDATE " . table($table) . " SET " . implode(", ", $update) . " WHERE " . implode(" AND ", $where)) && connection()->affected_rows)
|| queries("INSERT INTO " . table($table) . " (" . implode(", ", array_keys($set)) . ") VALUES (" . implode(", ", $set) . ")"))
) {
return false;
@@ -264,13 +254,13 @@ if (isset($_GET["pgsql"])) {
return true;
}
function slowQuery($query, $timeout) {
function slowQuery(string $query, int $timeout) {
$this->conn->query("SET statement_timeout = " . (1000 * $timeout));
$this->conn->timeout = 1000 * $timeout;
return $query;
}
function convertSearch($idf, $val, $field) {
function convertSearch(string $idf, array $val, array $field): string {
$textTypes = "char|text";
if (strpos($val["op"], "LIKE") === false) {
$textTypes .= "|date|time(stamp)?|boolean|uuid|inet|cidr|macaddr|" . number_type();
@@ -279,7 +269,7 @@ if (isset($_GET["pgsql"])) {
return (preg_match("~$textTypes~", $field["type"]) ? $idf : "CAST($idf AS text)");
}
function quoteBinary($s) {
function quoteBinary(string $s): string {
return "'\\x" . bin2hex($s) . "'"; // available since PostgreSQL 8.1
}
@@ -287,7 +277,7 @@ if (isset($_GET["pgsql"])) {
return $this->conn->warnings();
}
function tableHelp($name, $is_view = false) {
function tableHelp(string $name, bool $is_view = false) {
$links = array(
"information_schema" => "infoschema",
"pg_catalog" => ($is_view ? "view" : "catalog"),
@@ -298,15 +288,15 @@ if (isset($_GET["pgsql"])) {
}
}
function supportsIndex($table_status) {
function supportsIndex(array $table_status): bool {
// returns true for "materialized view"
return $table_status["Engine"] != "view";
}
function hasCStyleEscapes() {
function hasCStyleEscapes(): bool {
static $c_style;
if ($c_style === null) {
$c_style = ($this->conn->result("SHOW standard_conforming_strings") == "off");
$c_style = (get_val("SHOW standard_conforming_strings", 0, $this->conn) == "off");
}
return $c_style;
}
@@ -322,32 +312,14 @@ if (isset($_GET["pgsql"])) {
return idf_escape($idf);
}
function connect($credentials) {
global $drivers;
$connection = new Db;
if ($connection->connect($credentials[0], $credentials[1], $credentials[2])) {
if (min_version(9, 0, $connection)) {
$connection->query("SET application_name = 'Adminer'");
}
$version = $connection->result("SELECT version()");
$connection->flavor = (preg_match('~CockroachDB~', $version) ? 'cockroach' : '');
$connection->server_info = preg_replace('~^\D*([\d.]+[-\w]*).*~', '\1', $version);
if ($connection->flavor == 'cockroach') { // we don't use "PostgreSQL / CockroachDB" by default because it's too long
$drivers[DRIVER] = "CockroachDB";
}
return $connection;
}
return $connection->error;
}
function get_databases() {
function get_databases($flush) {
return get_vals("SELECT datname FROM pg_database
WHERE datallowconn = TRUE AND has_database_privilege(datname, 'CONNECT')
ORDER BY datname");
}
function limit($query, $where, $limit, $offset = 0, $separator = " ") {
return " $query$where" . ($limit !== null ? $separator . "LIMIT $limit" . ($offset ? " OFFSET $offset" : "") : "");
return " $query$where" . ($limit ? $separator . "LIMIT $limit" . ($offset ? " OFFSET $offset" : "") : "");
}
function limit1($table, $query, $where, $separator = "\n") {
@@ -380,10 +352,9 @@ ORDER BY 1";
}
function count_tables($databases) {
global $connection;
$return = array();
foreach ($databases as $db) {
if ($connection->select_db($db)) {
if (connection()->select_db($db)) {
$return[$db] = count(tables_list());
}
}
@@ -414,7 +385,7 @@ WHERE relkind IN ('r', 'm', 'v', 'f', 'p')
) {
$return[$row["Name"]] = $row;
}
return ($name != "" ? $return[$name] : $return);
return $return;
}
function is_view($table_status) {
@@ -479,12 +450,9 @@ ORDER BY a.attnum") as $row
}
function indexes($table, $connection2 = null) {
global $connection;
if (!is_object($connection2)) {
$connection2 = $connection;
}
$connection2 = connection($connection2);
$return = array();
$table_oid = $connection2->result("SELECT oid FROM pg_class WHERE relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = current_schema()) AND relname = " . q($table));
$table_oid = get_val("SELECT oid FROM pg_class WHERE relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = current_schema()) AND relname = " . q($table), 0, $connection2);
$columns = get_key_vals("SELECT attnum, attname FROM pg_attribute WHERE attrelid = $table_oid AND attnum > 0", $connection2);
foreach (
get_rows("SELECT relname, indisunique::int, indisprimary::int, indkey, indoption, (indpred IS NOT NULL)::int as indispartial
@@ -501,7 +469,7 @@ ORDER BY indisprimary DESC, indisunique DESC", $connection2) as $row
$return[$relname]["columns"][] = $columns[$indkey];
}
foreach (explode(" ", $row["indoption"]) as $indoption) {
$return[$relname]["descs"][] = ($indoption & 1 ? '1' : null); // 1 - INDOPTION_DESC
$return[$relname]["descs"][] = (intval($indoption) & 1 ? '1' : null); // 1 - INDOPTION_DESC
}
}
$return[$relname]["lengths"] = array();
@@ -510,7 +478,6 @@ ORDER BY indisprimary DESC, indisunique DESC", $connection2) as $row
}
function foreign_keys($table) {
global $driver;
$return = array();
foreach (
get_rows("SELECT conname, condeferrable::int AS deferrable, pg_get_constraintdef(oid) AS definition
@@ -526,8 +493,8 @@ ORDER BY conkey, conname") as $row
$row['table'] = idf_unescape($match2[4]);
}
$row['target'] = array_map('Adminer\idf_unescape', array_map('trim', explode(',', $match[3])));
$row['on_delete'] = (preg_match("~ON DELETE ($driver->onActions)~", $match[4], $match2) ? $match2[1] : 'NO ACTION');
$row['on_update'] = (preg_match("~ON UPDATE ($driver->onActions)~", $match[4], $match2) ? $match2[1] : 'NO ACTION');
$row['on_delete'] = (preg_match("~ON DELETE (driver()->onActions)~", $match[4], $match2) ? $match2[1] : 'NO ACTION');
$row['on_update'] = (preg_match("~ON UPDATE (driver()->onActions)~", $match[4], $match2) ? $match2[1] : 'NO ACTION');
$return[$row['conname']] = $row;
}
}
@@ -548,8 +515,7 @@ ORDER BY conkey, conname") as $row
}
function error() {
global $connection;
$return = h($connection->error);
$return = h(connection()->error);
if (preg_match('~^(.*\n)?([^\n]*)\n( *)\^(\n.*)?$~s', $return, $match)) {
$return = $match[1] . preg_replace('~((?:[^&]|&[^;]*;){' . strlen($match[3]) . '})(.*)~', '\1<b>\2</b>', $match[2]) . $match[4];
}
@@ -561,14 +527,12 @@ ORDER BY conkey, conname") as $row
}
function drop_databases($databases) {
global $connection;
$connection->close();
connection()->close();
return apply_queries("DROP DATABASE", $databases, 'Adminer\idf_escape');
}
function rename_database($name, $collation) {
global $connection;
$connection->close();
connection()->close();
return queries("ALTER DATABASE " . idf_escape(DB) . " RENAME TO " . idf_escape($name));
}
@@ -683,7 +647,7 @@ ORDER BY conkey, conname") as $row
function drop_tables($tables) {
foreach ($tables as $table) {
$status = table_status($table);
$status = table_status1($table);
if (!queries("DROP " . strtoupper($status["Engine"]) . " " . table($table))) {
return false;
}
@@ -693,7 +657,7 @@ ORDER BY conkey, conname") as $row
function move_tables($tables, $views, $target) {
foreach (array_merge($tables, $views) as $table) {
$status = table_status($table);
$status = table_status1($table);
if (!queries("ALTER " . strtoupper($status["Engine"]) . " " . table($table) . " SET SCHEMA " . idf_escape($target))) {
return false;
}
@@ -750,7 +714,7 @@ ORDER BY event_manipulation DESC") as $row
$rows = get_rows('SELECT routine_definition AS definition, LOWER(external_language) AS language, *
FROM information_schema.routines
WHERE routine_schema = current_schema() AND specific_name = ' . q($name));
$return = $rows[0];
$return = idx($rows, 0, array());
$return["returns"] = array("type" => $return["type_udt_name"]);
$return["fields"] = get_rows('SELECT parameter_name AS field, data_type AS type, character_maximum_length AS length, parameter_mode AS inout
FROM information_schema.parameters
@@ -780,7 +744,8 @@ ORDER BY SPECIFIC_NAME');
}
function last_id($result) {
return (is_object($result) && $result->num_rows ? $result->fetch_column(0) : 0);
$row = (is_object($result) ? $result->fetch_row() : array());
return ($row ? $row[0] : 0);
}
function explain($connection, $query) {
@@ -791,10 +756,9 @@ ORDER BY SPECIFIC_NAME');
if (preg_match("~ rows=([0-9]+)~", get_val("EXPLAIN SELECT * FROM " . idf_escape($table_status["Name"]) . ($where ? " WHERE " . implode(" AND ", $where) : "")), $regs)) {
return $regs[1];
}
return false;
}
function types() {
function types(): array {
return get_key_vals(
"SELECT oid, typname
FROM pg_type
@@ -819,12 +783,11 @@ AND typelem = 0"
}
function set_schema($schema, $connection2 = null) {
global $connection, $driver;
if (!$connection2) {
$connection2 = $connection;
$connection2 = connection();
}
$return = $connection2->query("SET search_path TO " . idf_escape($schema));
$driver->setUserTypes(types()); //! get types from current_schemas('t')
driver()->setUserTypes(types()); //! get types from current_schemas('t')
return $return;
}
@@ -834,7 +797,7 @@ AND typelem = 0"
function foreign_keys_sql($table) {
$return = "";
$status = table_status($table);
$status = table_status1($table);
$fkeys = foreign_keys($table);
ksort($fkeys);
@@ -846,18 +809,17 @@ AND typelem = 0"
}
function create_sql($table, $auto_increment, $style) {
global $driver;
$return_parts = array();
$sequences = array();
$status = table_status($table);
$status = table_status1($table);
if (is_view($status)) {
$view = view($table);
return rtrim("CREATE VIEW " . idf_escape($table) . " AS $view[select]", ";");
}
$fields = fields($table);
if (!$status || empty($fields)) {
if (count($status) < 2 || empty($fields)) {
return false;
}
@@ -867,7 +829,7 @@ AND typelem = 0"
foreach ($fields as $field) {
$part = idf_escape($field['field']) . ' ' . $field['full_type']
. default_value($field)
. ($field['attnotnull'] ? " NOT NULL" : "");
. ($field['null'] ? "" : " NOT NULL");
$return_parts[] = $part;
// sequences for fields
@@ -898,7 +860,7 @@ AND typelem = 0"
}
}
foreach ($driver->checkConstraints($table) as $conname => $consrc) {
foreach (driver()->checkConstraints($table) as $conname => $consrc) {
$return_parts[] = "CONSTRAINT " . idf_escape($conname) . " CHECK $consrc";
}
@@ -927,7 +889,7 @@ AND typelem = 0"
}
function trigger_sql($table) {
$status = table_status($table);
$status = table_status1($table);
$return = "";
foreach (triggers($table) as $trg_id => $trg) {
$trigger = trigger($trg_id, $status['Name']);
@@ -957,9 +919,8 @@ AND typelem = 0"
}
function support($feature) {
global $connection;
return preg_match('~^(check|database|table|columns|sql|indexes|descidx|comment|view|' . (min_version(9.3) ? 'materializedview|' : '') . 'scheme|' . (min_version(11) ? 'procedure|' : '') . 'routine|sequence|trigger|type|variables|drop_col'
. ($connection->flavor == 'cockroach' ? '' : '|processlist') // https://github.com/cockroachdb/cockroach/issues/24745
. (connection()->flavor == 'cockroach' ? '' : '|processlist') // https://github.com/cockroachdb/cockroach/issues/24745
. '|kill|dump)$~', $feature)
;
}

View File

@@ -1,23 +1,24 @@
<?php
namespace Adminer;
$drivers["sqlite"] = "SQLite";
add_driver("sqlite", "SQLite");
if (isset($_GET["sqlite"])) {
define('Adminer\DRIVER', "sqlite");
if (class_exists("SQLite3") && $_GET["ext"] != "pdo") {
class SqliteDb {
public $extension = "SQLite3", $server_info, $affected_rows, $errno, $error;
abstract class SqliteDb extends SqlDb {
public string $extension = "SQLite3";
private $link;
function __construct($filename) {
function attach(?string $filename, string $username, string $password): string {
$this->link = new \SQLite3($filename);
$version = $this->link->version();
$this->server_info = $version["versionString"];
return '';
}
function query($query) {
function query(string $query, bool $unbuffered = false) {
$result = @$this->link->query($query);
$this->error = "";
if (!$result) {
@@ -31,25 +32,12 @@ if (isset($_GET["sqlite"])) {
return true;
}
function quote($string) {
function quote(string $string): string {
return (is_utf8($string)
? "'" . $this->link->escapeString($string) . "'"
: "x'" . first(unpack('H*', $string)) . "'"
);
}
function store_result() {
return $this->result;
}
function result($query, $field = 0) {
$result = $this->query($query);
if (!is_object($result)) {
return false;
}
$row = $result->fetch_row();
return $row ? $row[$field] : false;
}
}
class Result {
@@ -68,7 +56,7 @@ if (isset($_GET["sqlite"])) {
return $this->result->fetchArray(SQLITE3_NUM);
}
function fetch_field() {
function fetch_field(): \stdClass {
$column = $this->offset++;
$type = $this->result->columnType($column);
return (object) array(
@@ -79,20 +67,19 @@ if (isset($_GET["sqlite"])) {
}
function __destruct() {
return $this->result->finalize();
$this->result->finalize();
}
}
} elseif (extension_loaded("pdo_sqlite")) {
class SqliteDb extends PdoDb {
public $extension = "PDO_SQLite";
abstract class SqliteDb extends PdoDb {
public string $extension = "PDO_SQLite";
function __construct($filename) {
function attach(?string $filename, string $username, string $password): string {
$this->dsn(DRIVER . ":$filename", "", "");
}
function select_db($db) {
return false;
$this->query("PRAGMA foreign_keys = 1");
$this->query("PRAGMA busy_timeout = 500");
return '';
}
}
@@ -100,19 +87,16 @@ if (isset($_GET["sqlite"])) {
if (class_exists('Adminer\SqliteDb')) {
class Db extends SqliteDb {
public $flavor = '';
function __construct() {
parent::__construct(":memory:");
function attach(?string $filename, string $username, string $password): string {
parent::attach($filename, $username, $password);
$this->query("PRAGMA foreign_keys = 1");
$this->query("PRAGMA busy_timeout = 500");
return '';
}
function select_db($filename) {
if (is_readable($filename) && $this->query("ATTACH " . $this->quote(preg_match("~(^[/\\\\]|:)~", $filename) ? $filename : dirname($_SERVER["SCRIPT_FILENAME"]) . "/$filename") . " AS a")) { // is_readable - SQLite 3
parent::__construct($filename);
$this->query("PRAGMA foreign_keys = 1");
$this->query("PRAGMA busy_timeout = 500");
return true;
function select_db(string $filename): bool {
if (is_readable($filename) && $this->query("ATTACH " . $this->quote(preg_match("~(^[/\\\\]|:)~", $filename) ? $filename : dirname($_SERVER["SCRIPT_FILENAME"]) . "/$filename") . " AS a")) {
return !self::attach($filename, '', '');
}
return false;
}
@@ -122,37 +106,41 @@ if (isset($_GET["sqlite"])) {
class Driver extends SqlDriver {
static $possibleDrivers = array("SQLite3", "PDO_SQLite");
static $jush = "sqlite";
static array $extensions = array("SQLite3", "PDO_SQLite");
static string $jush = "sqlite";
protected $types = array(array("integer" => 0, "real" => 0, "numeric" => 0, "text" => 0, "blob" => 0));
protected array $types = array(array("integer" => 0, "real" => 0, "numeric" => 0, "text" => 0, "blob" => 0));
public $editFunctions = array(
array(
// "text" => "date('now')/time('now')/datetime('now')",
), array(
"integer|real|numeric" => "+/-",
// "text" => "date/time/datetime",
"text" => "||",
)
public array $insertFunctions = array(); // "text" => "date('now')/time('now')/datetime('now')",
public array $editFunctions = array(
"integer|real|numeric" => "+/-",
// "text" => "date/time/datetime",
"text" => "||",
);
public $operators = array("=", "<", ">", "<=", ">=", "!=", "LIKE", "LIKE %%", "IN", "IS NULL", "NOT LIKE", "NOT IN", "IS NOT NULL", "SQL"); // REGEXP can be user defined function
public $functions = array("hex", "length", "lower", "round", "unixepoch", "upper");
public $grouping = array("avg", "count", "count distinct", "group_concat", "max", "min", "sum");
public array $operators = array("=", "<", ">", "<=", ">=", "!=", "LIKE", "LIKE %%", "IN", "IS NULL", "NOT LIKE", "NOT IN", "IS NOT NULL", "SQL"); // REGEXP can be user defined function
public array $functions = array("hex", "length", "lower", "round", "unixepoch", "upper");
public array $grouping = array("avg", "count", "count distinct", "group_concat", "max", "min", "sum");
function __construct($connection) {
static function connect(?string $server, string $username, string $password) {
if ($password != "") {
return lang('Database does not support password.');
}
return parent::connect(":memory:", "", "");
}
function __construct(Db $connection) {
parent::__construct($connection);
if (min_version(3.31, 0, $connection)) {
$this->generated = array("STORED", "VIRTUAL");
}
}
function structuredTypes() {
function structuredTypes(): array {
return array_keys($this->types[0]);
}
function insertUpdate($table, $rows, $primary) {
function insertUpdate(string $table, array $rows, array $primary) {
$values = array();
foreach ($rows as $set) {
$values[] = "(" . implode(", ", $set) . ")";
@@ -160,7 +148,7 @@ if (isset($_GET["sqlite"])) {
return queries("REPLACE INTO " . table($table) . " (" . implode(", ", array_keys(reset($rows))) . ") VALUES\n" . implode(",\n", $values));
}
function tableHelp($name, $is_view = false) {
function tableHelp(string $name, bool $is_view = false) {
if ($name == "sqlite_sequence") {
return "fileformat2.html#seqtab";
}
@@ -169,10 +157,20 @@ if (isset($_GET["sqlite"])) {
}
}
function checkConstraints($table) {
preg_match_all('~ CHECK *(\( *(((?>[^()]*[^() ])|(?1))*) *\))~', $this->conn->result("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = " . q($table)), $matches); //! could be inside a comment
function checkConstraints(string $table): array {
preg_match_all('~ CHECK *(\( *(((?>[^()]*[^() ])|(?1))*) *\))~', get_val("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = " . q($table), 0, $this->conn), $matches); //! could be inside a comment
return array_combine($matches[2], $matches[2]);
}
function allFields(): array {
$return = array();
foreach (tables_list() as $table => $type) {
foreach (fields($table) as $field) {
$return[$table][] = $field;
}
}
return $return;
}
}
@@ -185,20 +183,12 @@ if (isset($_GET["sqlite"])) {
return idf_escape($idf);
}
function connect($credentials) {
list(, , $password) = $credentials;
if ($password != "") {
return lang('Database does not support password.');
}
return new Db;
}
function get_databases() {
function get_databases($flush) {
return array();
}
function limit($query, $where, $limit, $offset = 0, $separator = " ") {
return " $query$where" . ($limit !== null ? $separator . "LIMIT $limit" . ($offset ? " OFFSET $offset" : "") : "");
return " $query$where" . ($limit ? $separator . "LIMIT $limit" . ($offset ? " OFFSET $offset" : "") : "");
}
function limit1($table, $query, $where, $separator = "\n") {
@@ -230,10 +220,10 @@ if (isset($_GET["sqlite"])) {
$row["Rows"] = get_val("SELECT COUNT(*) FROM " . idf_escape($row["Name"]));
$return[$row["Name"]] = $row;
}
foreach (get_rows("SELECT * FROM sqlite_sequence", null, "") as $row) {
foreach (get_rows("SELECT * FROM sqlite_sequence" . ($name != "" ? " WHERE name = " . q($name) : ""), null, "") as $row) {
$return[$row["name"]]["Auto_increment"] = $row["seq"];
}
return ($name != "" ? $return[$name] : $return);
return $return;
}
function is_view($table_status) {
@@ -288,12 +278,9 @@ if (isset($_GET["sqlite"])) {
}
function indexes($table, $connection2 = null) {
global $connection;
if (!is_object($connection2)) {
$connection2 = $connection;
}
$connection2 = connection($connection2);
$return = array();
$sql = $connection2->result("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = " . q($table));
$sql = get_val("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = " . q($table), 0, $connection2);
if (preg_match('~\bPRIMARY\s+KEY\s*\((([^)"]+|"[^"]*"|`[^`]*`)++)~i', $sql, $match)) {
$return[""] = array("type" => "PRIMARY", "columns" => array(), "lengths" => array(), "descs" => array());
preg_match_all('~((("[^"]*+")+|(?:`[^`]*+`)+)|(\S+))(\s+(ASC|DESC))?(,\s*|$)~i', $match[1], $matches, PREG_SET_ORDER);
@@ -360,34 +347,32 @@ if (isset($_GET["sqlite"])) {
}
function error() {
global $connection;
return h($connection->error);
return h(connection()->error);
}
function check_sqlite_name($name) {
// avoid creating PHP files on unsecured servers
global $connection;
$extensions = "db|sdb|sqlite";
if (!preg_match("~^[^\\0]*\\.($extensions)\$~", $name)) {
$connection->error = lang('Please use one of the extensions %s.', str_replace("|", ", ", $extensions));
connection()->error = lang('Please use one of the extensions %s.', str_replace("|", ", ", $extensions));
return false;
}
return true;
}
function create_database($db, $collation) {
global $connection;
if (file_exists($db)) {
$connection->error = lang('File exists.');
connection()->error = lang('File exists.');
return false;
}
if (!check_sqlite_name($db)) {
return false;
}
try {
$link = new SqliteDb($db);
$link = new Db();
$link->attach($db, '', '');
} catch (\Exception $ex) {
$connection->error = $ex->getMessage();
connection()->error = $ex->getMessage();
return false;
}
$link->query('PRAGMA encoding = "UTF-8"');
@@ -397,11 +382,10 @@ if (isset($_GET["sqlite"])) {
}
function drop_databases($databases) {
global $connection;
$connection->__construct(":memory:"); // to unlock file, doesn't work in PDO on Windows
connection()->attach(":memory:", '', ''); // to unlock file, doesn't work in PDO on Windows
foreach ($databases as $db) {
if (!@unlink($db)) {
$connection->error = lang('File exists.');
connection()->error = lang('File exists.');
return false;
}
}
@@ -409,12 +393,11 @@ if (isset($_GET["sqlite"])) {
}
function rename_database($name, $collation) {
global $connection;
if (!check_sqlite_name($name)) {
return false;
}
$connection->__construct(":memory:");
$connection->error = lang('File exists.');
connection()->attach(":memory:", '', '');
connection()->error = lang('File exists.');
return @rename(DB, $name);
}
@@ -423,7 +406,6 @@ if (isset($_GET["sqlite"])) {
}
function alter_table($table, $name, $fields, $foreign, $comment, $engine, $collation, $auto_increment, $partitioning) {
global $connection;
$use_all_fields = ($table == "" || $foreign);
foreach ($fields as $field) {
if ($field[0] != "" || !$field[1] || $field[2]) {
@@ -456,7 +438,7 @@ if (isset($_GET["sqlite"])) {
if ($auto_increment) {
queries("BEGIN");
queries("UPDATE sqlite_sequence SET seq = $auto_increment WHERE name = " . q($name)); // ignores error
if (!$connection->affected_rows) {
if (!connection()->affected_rows) {
queries("INSERT INTO sqlite_sequence (name, seq) VALUES (" . q($name) . ", $auto_increment)");
}
queries("COMMIT");
@@ -465,19 +447,17 @@ if (isset($_GET["sqlite"])) {
}
/** Recreate table
* @param string original name
* @param string new name
* @param array [process_field()], empty to preserve
* @param array [$original => idf_escape($new_column)], empty to preserve
* @param string [format_foreign_key()], empty to preserve
* @param int set auto_increment to this value, 0 to preserve
* @param array [[$type, $name, $columns]], empty to preserve
* @param string CHECK constraint to drop
* @param string CHECK constraint to add
* @return bool
* @param string $table original name
* @param string $name new name
* @param list<list<string>> $fields [process_field()], empty to preserve
* @param string[] $originals [$original => idf_escape($new_column)], empty to preserve
* @param string[] $foreign [format_foreign_key()], empty to preserve
* @param numeric-string $auto_increment set auto_increment to this value, "" to preserve
* @param list<array{string, string, list<string>|'DROP'}> $indexes [[$type, $name, $columns]], empty to preserve
* @param string $drop_check CHECK constraint to drop
* @param string $add_check CHECK constraint to add
*/
function recreate_table($table, $name, $fields, $originals, $foreign, $auto_increment = 0, $indexes = array(), $drop_check = "", $add_check = "") {
global $driver;
function recreate_table(string $table, string $name, array $fields, array $originals, array $foreign, string $auto_increment = "", $indexes = array(), string $drop_check = "", string $add_check = ""): bool {
if ($table != "") {
if (!$fields) {
foreach (fields($table) as $key => $field) {
@@ -534,24 +514,25 @@ if (isset($_GET["sqlite"])) {
}
queries("BEGIN");
}
foreach ($fields as $key => $field) {
$changes = array();
foreach ($fields as $field) {
if (preg_match('~GENERATED~', $field[3])) {
unset($originals[array_search($field[0], $originals)]);
}
$fields[$key] = " " . implode($field);
$changes[] = " " . implode($field);
}
$fields = array_merge($fields, array_filter($foreign));
foreach ($driver->checkConstraints($table) as $check) {
$changes = array_merge($changes, array_filter($foreign));
foreach (driver()->checkConstraints($table) as $check) {
if ($check != $drop_check) {
$fields[] = " CHECK ($check)";
$changes[] = " CHECK ($check)";
}
}
if ($add_check) {
$fields[] = " CHECK ($add_check)";
$changes[] = " CHECK ($add_check)";
}
$temp_name = ($table == $name ? "adminer_$name" : $name);
if (!queries("CREATE TABLE " . table($temp_name) . " (\n" . implode(",\n", $fields) . "\n)")) {
// implicit ROLLBACK to not overwrite $connection->error
if (!queries("CREATE TABLE " . table($temp_name) . " (\n" . implode(",\n", $changes) . "\n)")) {
// implicit ROLLBACK to not overwrite connection()->error
return false;
}
if ($table != "") {
@@ -560,10 +541,10 @@ if (isset($_GET["sqlite"])) {
}
$triggers = array();
foreach (triggers($table) as $trigger_name => $timing_event) {
$trigger = trigger($trigger_name);
$trigger = trigger($trigger_name, $table);
$triggers[] = "CREATE TRIGGER " . idf_escape($trigger_name) . " " . implode(" ", $timing_event) . " ON " . table($name) . "\n$trigger[Statement]";
}
$auto_increment = $auto_increment ? 0 : get_val("SELECT seq FROM sqlite_sequence WHERE name = " . q($table)); // if $auto_increment is set then it will be updated later
$auto_increment = $auto_increment ? "" : get_val("SELECT seq FROM sqlite_sequence WHERE name = " . q($table)); // if $auto_increment is set then it will be updated later
if (
!queries("DROP TABLE " . table($table)) // drop before creating indexes and triggers to allow using old names
|| ($table == $name && !queries("ALTER TABLE " . table($temp_name) . " RENAME TO " . table($name)))
@@ -595,7 +576,7 @@ if (isset($_GET["sqlite"])) {
function alter_indexes($table, $alter) {
foreach ($alter as $primary) {
if ($primary[0] == "PRIMARY") {
return recreate_table($table, $table, array(), array(), array(), 0, $alter);
return recreate_table($table, $table, array(), array(), array(), "", $alter);
}
}
foreach (array_reverse($alter) as $val) {
@@ -626,7 +607,7 @@ if (isset($_GET["sqlite"])) {
return false;
}
function trigger($name) {
function trigger($name, $table) {
if ($name == "") {
return array("Statement" => "BEGIN\n\t;\nEND");
}
@@ -680,7 +661,7 @@ if (isset($_GET["sqlite"])) {
function found_rows($table_status, $where) {
}
function types() {
function types(): array {
return array();
}

View File

@@ -16,7 +16,7 @@ if ($_POST && !$error) {
$is_sql = preg_match('~sql~', $_POST["format"]);
if ($is_sql) {
echo "-- Adminer $VERSION " . $drivers[DRIVER] . " " . str_replace("\n", " ", $connection->server_info) . " dump\n\n";
echo "-- Adminer " . VERSION . " " . get_driver(DRIVER) . " " . str_replace("\n", " ", connection()->server_info) . " dump\n\n";
if (JUSH == "sql") {
echo "SET NAMES utf8;
SET time_zone = '+00:00';
@@ -24,8 +24,8 @@ SET foreign_key_checks = 0;
" . ($_POST["data_style"] ? "SET sql_mode = 'NO_AUTO_VALUE_ON_ZERO';
" : "") . "
";
$connection->query("SET time_zone = '+00:00'");
$connection->query("SET sql_mode = ''");
connection()->query("SET time_zone = '+00:00'");
connection()->query("SET sql_mode = ''");
}
}
@@ -39,8 +39,8 @@ SET foreign_key_checks = 0;
}
foreach ((array) $databases as $db) {
$adminer->dumpDatabase($db);
if ($connection->select_db($db)) {
adminer()->dumpDatabase($db);
if (connection()->select_db($db)) {
if ($is_sql && preg_match('~CREATE~', $style) && ($create = get_val("SHOW CREATE DATABASE " . idf_escape($db), 1))) {
set_utf8mb4($create);
if ($style == "DROP+CREATE") {
@@ -93,17 +93,18 @@ SET foreign_key_checks = 0;
$table = (DB == "" || in_array($name, (array) $_POST["tables"]));
$data = (DB == "" || in_array($name, (array) $_POST["data"]));
if ($table || $data) {
$tmp_file = null;
if ($ext == "tar") {
$tmp_file = new TmpFile;
ob_start(array($tmp_file, 'write'), 1e5);
}
$adminer->dumpTable($name, ($table ? $_POST["table_style"] : ""), (is_view($table_status) ? 2 : 0));
adminer()->dumpTable($name, ($table ? $_POST["table_style"] : ""), (is_view($table_status) ? 2 : 0));
if (is_view($table_status)) {
$views[] = $name;
} elseif ($data) {
$fields = fields($name);
$adminer->dumpData($name, $_POST["data_style"], "SELECT *" . convert_fields($fields, $fields) . " FROM " . table($name));
adminer()->dumpData($name, $_POST["data_style"], "SELECT *" . convert_fields($fields, $fields) . " FROM " . table($name));
}
if ($is_sql && $_POST["triggers"] && $table && ($triggers = trigger_sql($name))) {
echo "\nDELIMITER ;;\n$triggers\nDELIMITER ;\n";
@@ -129,7 +130,7 @@ SET foreign_key_checks = 0;
}
foreach ($views as $view) {
$adminer->dumpTable($view, $_POST["table_style"], 1);
adminer()->dumpTable($view, $_POST["table_style"], 1);
}
if ($ext == "tar") {
@@ -139,7 +140,7 @@ SET foreign_key_checks = 0;
}
}
$adminer->dumpFooter();
adminer()->dumpFooter();
exit;
}
@@ -164,9 +165,9 @@ if (!isset($row["events"])) { // backwards compatibility
$row["triggers"] = $row["table_style"];
}
echo "<tr><th>" . lang('Output') . "<td>" . html_radios("output", $adminer->dumpOutput(), $row["output"]) . "\n";
echo "<tr><th>" . lang('Output') . "<td>" . html_radios("output", adminer()->dumpOutput(), $row["output"]) . "\n";
echo "<tr><th>" . lang('Format') . "<td>" . html_radios("format", $adminer->dumpFormat(), $row["format"]) . "\n";
echo "<tr><th>" . lang('Format') . "<td>" . html_radios("format", adminer()->dumpFormat(), $row["format"]) . "\n";
echo (JUSH == "sqlite" ? "" : "<tr><th>" . lang('Database') . "<td>" . html_select('db_style', $db_style, $row["db_style"])
. (support("type") ? checkbox("types", 1, $row["types"], lang('User types')) : "")
@@ -220,7 +221,7 @@ if (DB != "") {
echo "<label class='block'><input type='checkbox' id='check-databases'" . ($TABLE == "" ? " checked" : "") . ">" . lang('Database') . "</label>";
echo script("qs('#check-databases').onclick = partial(formCheck, /^databases\\[/);", "");
echo "</thead>\n";
$databases = $adminer->databases();
$databases = adminer()->databases();
if ($databases) {
foreach ($databases as $db) {
if (!information_schema($db)) {

View File

@@ -3,10 +3,13 @@ namespace Adminer;
$TABLE = $_GET["edit"];
$fields = fields($TABLE);
$where = (isset($_GET["select"]) ? ($_POST["check"] && count($_POST["check"]) == 1 ? where_check($_POST["check"][0], $fields) : "") : where($_GET, $fields));
$where = (isset($_GET["select"])
? ($_POST["check"] && count($_POST["check"]) == 1 ? where_check($_POST["check"][0], $fields) : "")
: where($_GET, $fields)
);
$update = (isset($_GET["select"]) ? $_POST["edit"] : $where);
foreach ($fields as $name => $field) {
if (!isset($field["privileges"][$update ? "update" : "insert"]) || $adminer->fieldName($field) == "" || $field["generated"]) {
if (!isset($field["privileges"][$update ? "update" : "insert"]) || adminer()->fieldName($field) == "" || $field["generated"]) {
unset($fields[$name]);
}
}
@@ -27,7 +30,7 @@ if ($_POST && !$error && !isset($_GET["select"])) {
queries_redirect(
$location,
lang('Item has been deleted.'),
$driver->delete($TABLE, $query_where, !$unique_array)
driver()->delete($TABLE, $query_where, $unique_array ? 0 : 1)
);
} else {
@@ -46,7 +49,7 @@ if ($_POST && !$error && !isset($_GET["select"])) {
queries_redirect(
$location,
lang('Item has been updated.'),
$driver->update($TABLE, $set, $query_where, !$unique_array)
driver()->update($TABLE, $set, $query_where, $unique_array ? 0 : 1)
);
if (is_ajax()) {
page_headers();
@@ -54,7 +57,7 @@ if ($_POST && !$error && !isset($_GET["select"])) {
exit;
}
} else {
$result = $driver->insert($TABLE, $set);
$result = driver()->insert($TABLE, $set);
$last_id = ($result ? last_id($result) : 0);
queries_redirect($location, lang('Item%s has been inserted.', ($last_id ? " $last_id" : "")), $result); //! link
}
@@ -77,7 +80,7 @@ if ($_POST["save"]) {
$select = array("*");
}
if ($select) {
$result = $driver->select($TABLE, $select, array($where), $select, array(), (isset($_GET["select"]) ? 2 : 1));
$result = driver()->select($TABLE, $select, array($where), $select, array(), (isset($_GET["select"]) ? 2 : 1));
if (!$result) {
$error = error();
} else {
@@ -92,12 +95,12 @@ if ($_POST["save"]) {
}
}
if (!support("table") && !$fields) {
if (!support("table") && !$fields) { // used by Mongo and SimpleDB
if (!$where) { // insert
$result = $driver->select($TABLE, array("*"), $where, array("*"));
$result = driver()->select($TABLE, array("*"), array(), array("*"));
$row = ($result ? $result->fetch_assoc() : false);
if (!$row) {
$row = array($driver->primary => "");
$row = array(driver()->primary => "");
}
}
if ($row) {
@@ -105,9 +108,9 @@ if (!support("table") && !$fields) {
if (!$where) {
$row[$key] = null;
}
$fields[$key] = array("field" => $key, "null" => ($key != $driver->primary), "auto_increment" => ($key == $driver->primary));
$fields[$key] = array("field" => $key, "null" => ($key != driver()->primary), "auto_increment" => ($key == driver()->primary));
}
}
}
edit_form($TABLE, $fields, $row, $update);
edit_form($TABLE, $fields, $row, $update, $error);

View File

@@ -2,10 +2,9 @@
// To create Adminer just for Elasticsearch, run `../compile.php elastic`.
function adminer_object() {
include_once "../plugins/plugin.php";
include_once "../plugins/login-password-less.php";
include_once "../plugins/drivers/elastic.php";
return new AdminerPlugin(array(
return new Adminer\Plugins(array(
// TODO: inline the result of password_hash() so that the password is not visible in source codes
new AdminerLoginPasswordLess(password_hash("YOUR_PASSWORD_HERE", PASSWORD_DEFAULT)),
));

View File

@@ -1,7 +1,7 @@
<?php
namespace Adminer;
if (substr($VERSION, -4) != '-dev') {
if (substr(VERSION, -4) != '-dev') {
if ($_SERVER["HTTP_IF_MODIFIED_SINCE"]) {
header("HTTP/1.1 304 Not Modified");
exit;
@@ -35,24 +35,5 @@ if ($_GET["file"] == "favicon.ico") {
../externals/jush/modules/jush-mssql.js;
../externals/jush/modules/jush-oracle.js;
../externals/jush/modules/jush-simpledb.js', 'minify_js'));
} else {
header("Content-Type: image/gif");
switch ($_GET["file"]) {
case "plus.gif":
echo compile_file('../adminer/static/plus.gif');
break;
case "cross.gif":
echo compile_file('../adminer/static/cross.gif');
break;
case "up.gif":
echo compile_file('../adminer/static/up.gif');
break;
case "down.gif":
echo compile_file('../adminer/static/down.gif');
break;
case "arrow.gif":
echo compile_file('../adminer/static/arrow.gif');
break;
}
}
exit;

View File

@@ -58,7 +58,7 @@ if ($_POST) {
<?php
$source = array_keys(fields($TABLE)); //! no text and blob
if ($row["db"] != "") {
$connection->select_db($row["db"]);
connection()->select_db($row["db"]);
}
if ($row["ns"] != "") {
$orig_schema = get_schema();
@@ -69,7 +69,7 @@ $target = array_keys(fields(in_array($row["table"], $referencable) ? $row["table
$onchange = "this.form['change-js'].value = '1'; this.form.submit();";
echo "<p>" . lang('Target table') . ": " . html_select("table", $referencable, $row["table"], $onchange) . "\n";
if (support("scheme")) {
$schemas = array_filter($adminer->schemas(), function ($schema) {
$schemas = array_filter(adminer()->schemas(), function ($schema) {
return !preg_match('~^information_schema$~i', $schema);
});
echo lang('Schema') . ": " . html_select("ns", $schemas, $row["ns"] != "" ? $row["ns"] : $_GET["ns"], $onchange);
@@ -78,7 +78,7 @@ if (support("scheme")) {
}
} elseif (JUSH != "sqlite") {
$dbs = array();
foreach ($adminer->databases() as $db) {
foreach (adminer()->databases() as $db) {
if (!information_schema($db)) {
$dbs[] = $db;
}
@@ -95,14 +95,14 @@ $j = 0;
foreach ($row["source"] as $key => $val) {
echo "<tr>";
echo "<td>" . html_select("source[" . (+$key) . "]", array(-1 => "") + $source, $val, ($j == count($row["source"]) - 1 ? "foreignAddRow.call(this);" : ""), "label-source");
echo "<td>" . html_select("target[" . (+$key) . "]", $target, $row["target"][$key], "", "label-target");
echo "<td>" . html_select("target[" . (+$key) . "]", $target, idx($row["target"], $key), "", "label-target");
$j++;
}
?>
</table>
<p>
<?php echo lang('ON DELETE'); ?>: <?php echo html_select("on_delete", array(-1 => "") + explode("|", $driver->onActions), $row["on_delete"]); ?>
<?php echo lang('ON UPDATE'); ?>: <?php echo html_select("on_update", array(-1 => "") + explode("|", $driver->onActions), $row["on_update"]); ?>
<?php echo lang('ON DELETE'); ?>: <?php echo html_select("on_delete", array(-1 => "") + explode("|", driver()->onActions), $row["on_delete"]); ?>
<?php echo lang('ON UPDATE'); ?>: <?php echo html_select("on_update", array(-1 => "") + explode("|", driver()->onActions), $row["on_update"]); ?>
<?php echo doc_link(array(
'sql' => "innodb-foreign-key-constraints.html",
'mariadb' => "foreign-keys/",

File diff suppressed because it is too large Load Diff

View File

@@ -1,14 +1,6 @@
<?php
namespace Adminer;
$connection = '';
$has_token = $_SESSION["token"];
if (!$has_token) {
$_SESSION["token"] = rand(1, 1e6); // defense against cross-site request forgery
}
$token = get_token(); ///< @var string CSRF protection
$permanent = array();
if ($_COOKIE["adminer_permanent"]) {
foreach (explode(" ", $_COOKIE["adminer_permanent"]) as $val) {
@@ -17,8 +9,7 @@ if ($_COOKIE["adminer_permanent"]) {
}
}
function add_invalid_login() {
global $adminer;
function add_invalid_login(): void {
$base = get_temp_dir() . "/adminer.invalid";
// adminer.invalid may not be writable by us, try the files with random suffixes
foreach (glob("$base*") ?: array($base) as $filename) {
@@ -42,7 +33,7 @@ function add_invalid_login() {
}
}
}
$invalid = &$invalids[$adminer->bruteForceKey()];
$invalid = &$invalids[adminer()->bruteForceKey()];
if (!$invalid) {
$invalid = array($time + 30*60, 0); // active for 30 minutes
}
@@ -50,8 +41,8 @@ function add_invalid_login() {
file_write_unlock($fp, serialize($invalids));
}
function check_invalid_login() {
global $adminer;
/** @param string[] $permanent */
function check_invalid_login(array &$permanent): void {
$invalids = array();
foreach (glob(get_temp_dir() . "/adminer.invalid*") as $filename) {
$fp = file_open_lock($filename);
@@ -61,10 +52,11 @@ function check_invalid_login() {
break;
}
}
$invalid = ($invalids ? $invalids[$adminer->bruteForceKey()] : array());
/** @var array{int, int} */
$invalid = idx($invalids, adminer()->bruteForceKey(), array());
$next_attempt = ($invalid[1] > 29 ? $invalid[0] - time() : 0); // allow 30 invalid attempts
if ($next_attempt > 0) { //! do the same with permanent login
auth_error(lang('Too many unsuccessful logins, try again in %d minute(s).', ceil($next_attempt / 60)));
auth_error(lang('Too many unsuccessful logins, try again in %d minute(s).', ceil($next_attempt / 60)), $permanent);
}
}
@@ -80,7 +72,7 @@ if ($auth) {
$_SESSION["db"][$vendor][$server][$username][$db] = true;
if ($auth["permanent"]) {
$key = implode("-", array_map('base64_encode', array($vendor, $server, $username, $db)));
$private = $adminer->permanentLogin(true);
$private = adminer()->permanentLogin(true);
$permanent[$key] = "$key:" . base64_encode($private ? encrypt_string($password, $private) : "");
cookie("adminer_permanent", implode(" ", $permanent));
}
@@ -94,16 +86,16 @@ if ($auth) {
redirect(auth_url($vendor, $server, $username, $db));
}
} elseif ($_POST["logout"] && (!$has_token || verify_token())) {
} elseif ($_POST["logout"] && (!$_SESSION["token"] || verify_token())) {
foreach (array("pwds", "db", "dbs", "queries") as $key) {
set_session($key, null);
}
unset_permanent();
unset_permanent($permanent);
redirect(substr(preg_replace('~\b(username|db|ns)=[^&]*&~', '', ME), 0, -1), lang('Logout successful.') . ' ' . lang('Thanks for using Adminer, consider <a href="https://www.adminer.org/en/donation/">donating</a>.'));
} elseif ($permanent && !$_SESSION["pwds"]) {
session_regenerate_id();
$private = $adminer->permanentLogin();
$private = adminer()->permanentLogin();
foreach ($permanent as $key => $val) {
list(, $cipher) = explode(":", $val);
list($vendor, $server, $username, $db) = array_map('base64_decode', explode("-", $key));
@@ -112,8 +104,10 @@ if ($auth) {
}
}
function unset_permanent() {
global $permanent;
/** Remove credentials from permanent login
* @param string[] $permanent
*/
function unset_permanent(array &$permanent): void {
foreach ($permanent as $key => $val) {
list($vendor, $server, $username, $db) = array_map('base64_decode', explode("-", $key));
if ($vendor == DRIVER && $server == SERVER && $username == $_GET["username"] && $db == DB) {
@@ -124,15 +118,15 @@ function unset_permanent() {
}
/** Render an error message and a login form
* @param string plain text
* @return null exits
* @param string $error plain text
* @param string[] $permanent
* @return never
*/
function auth_error($error) {
global $adminer, $has_token;
function auth_error(string $error, array &$permanent) {
$session_name = session_name();
if (isset($_GET["username"])) {
header("HTTP/1.1 403 Forbidden"); // 401 requires sending WWW-Authenticate header
if (($_COOKIE[$session_name] || $_GET[$session_name]) && !$has_token) {
if (($_COOKIE[$session_name] || $_GET[$session_name]) && !$_SESSION["token"]) {
$error = lang('Session expired, please login again.');
} else {
restart_session();
@@ -144,7 +138,7 @@ function auth_error($error) {
}
set_password(DRIVER, SERVER, $_GET["username"], null);
}
unset_permanent();
unset_permanent($permanent);
}
}
if (!$_COOKIE[$session_name] && $_GET[$session_name] && ini_bool("session.use_only_cookies")) {
@@ -152,6 +146,9 @@ function auth_error($error) {
}
$params = session_get_cookie_params();
cookie("adminer_key", ($_COOKIE["adminer_key"] ?: rand_string()), $params["lifetime"]);
if (!$_SESSION["token"]) {
$_SESSION["token"] = rand(1, 1e6); // this is for next attempt
}
page_header(lang('Login'), $error, null);
echo "<form action='' method='post'>\n";
echo "<div>";
@@ -159,7 +156,7 @@ function auth_error($error) {
echo "<p class='message'>" . lang('The action will be performed after successful login with the same credentials.') . "\n";
}
echo "</div>\n";
$adminer->loginForm();
adminer()->loginForm();
echo "</form>\n";
page_footer("auth");
exit;
@@ -167,46 +164,49 @@ function auth_error($error) {
if (isset($_GET["username"]) && !class_exists('Adminer\Db')) {
unset($_SESSION["pwds"][DRIVER]);
unset_permanent();
page_header(lang('No extension'), lang('None of the supported PHP extensions (%s) are available.', implode(", ", Driver::$possibleDrivers)), false);
unset_permanent($permanent);
page_header(lang('No extension'), lang('None of the supported PHP extensions (%s) are available.', implode(", ", Driver::$extensions)), false);
page_footer("auth");
exit;
}
stop_session(true);
$connection = '';
if (isset($_GET["username"]) && is_string(get_password())) {
list($host, $port) = explode(":", SERVER, 2);
if (preg_match('~^\s*([-+]?\d+)~', $port, $match) && ($match[1] < 1024 || $match[1] > 65535)) { // is_numeric('80#') would still connect to port 80
auth_error(lang('Connecting to privileged ports is not allowed.'));
auth_error(lang('Connecting to privileged ports is not allowed.'), $permanent);
}
check_invalid_login();
$connection = connect($adminer->credentials());
check_invalid_login($permanent);
$credentials = adminer()->credentials();
$connection = Driver::connect($credentials[0], $credentials[1], $credentials[2]);
if (is_object($connection)) {
$driver = new Driver($connection);
if ($adminer->operators === null) {
$adminer->operators = $driver->operators;
}
if (Driver::$jush == 'sql' || $connection->flavor == 'cockroach') {
save_settings(array("vendor-" . DRIVER . "-" . SERVER => $drivers[DRIVER]));
Db::$instance = $connection;
Driver::$instance = new Driver($connection);
if ($connection->flavor) {
save_settings(array("vendor-" . DRIVER . "-" . SERVER => get_driver(DRIVER)));
}
}
}
$login = null;
if (!is_object($connection) || ($login = $adminer->login($_GET["username"], get_password())) !== true) {
$error = (is_string($connection) ? nl_br(h($connection)) : (is_string($login) ? $login : lang('Invalid credentials.')));
auth_error($error . (preg_match('~^ | $~', get_password()) ? '<br>' . lang('There is a space in the input password which might be the cause.') : ''));
if (!is_object($connection) || ($login = adminer()->login($_GET["username"], get_password())) !== true) {
$error = (is_string($connection) ? nl_br(h($connection)) : (is_string($login) ? $login : lang('Invalid credentials.')))
. (preg_match('~^ | $~', get_password()) ? '<br>' . lang('There is a space in the input password which might be the cause.') : '');
auth_error($error, $permanent);
}
if ($_POST["logout"] && $has_token && !verify_token()) {
if ($_POST["logout"] && $_SESSION["token"] && !verify_token()) {
page_header(lang('Logout'), lang('Invalid CSRF token. Send the form again.'));
page_footer("db");
exit;
}
if (!$_SESSION["token"]) {
$_SESSION["token"] = rand(1, 1e6); // defense against cross-site request forgery
}
stop_session(true);
if ($auth && $_POST["token"]) {
$_POST["token"] = $token; // reset token after explicit login
$_POST["token"] = get_token(); // reset token after explicit login
}
$error = ''; ///< @var string

View File

@@ -39,7 +39,7 @@ if ($_GET["script"] == "version") {
exit;
}
global $adminer, $connection, $driver, $drivers, $error, $HTTPS, $LANG, $langs, $permanent, $has_token, $token, $translations, $VERSION; // allows including Adminer inside a function
// Adminer doesn't use any global variables; they used to be declared here
if (!$_SERVER["REQUEST_URI"]) { // IIS 5 compatibility
$_SERVER["REQUEST_URI"] = $_SERVER["ORIG_PATH_INFO"];
@@ -50,13 +50,13 @@ if (!strpos($_SERVER["REQUEST_URI"], '?') && $_SERVER["QUERY_STRING"] != "") { /
if ($_SERVER["HTTP_X_FORWARDED_PREFIX"]) {
$_SERVER["REQUEST_URI"] = $_SERVER["HTTP_X_FORWARDED_PREFIX"] . $_SERVER["REQUEST_URI"];
}
$HTTPS = ($_SERVER["HTTPS"] && strcasecmp($_SERVER["HTTPS"], "off")) || ini_bool("session.cookie_secure"); // session.cookie_secure could be set on HTTP if we are behind a reverse proxy
define('Adminer\HTTPS', ($_SERVER["HTTPS"] && strcasecmp($_SERVER["HTTPS"], "off")) || ini_bool("session.cookie_secure")); // session.cookie_secure could be set on HTTP if we are behind a reverse proxy
@ini_set("session.use_trans_sid", false); // protect links in export, @ - may be disabled
@ini_set("session.use_trans_sid", '0'); // protect links in export, @ - may be disabled
if (!defined("SID")) {
session_cache_limiter(""); // to allow restarting session
session_name("adminer_sid"); // use specific session name to get own namespace
session_set_cookie_params(0, preg_replace('~\?.*~', '', $_SERVER["REQUEST_URI"]), "", $HTTPS, true); // ini_set() may be disabled
session_set_cookie_params(0, preg_replace('~\?.*~', '', $_SERVER["REQUEST_URI"]), "", HTTPS, true); // ini_set() may be disabled
session_start();
}
@@ -66,10 +66,11 @@ if (function_exists("get_magic_quotes_runtime") && get_magic_quotes_runtime()) {
set_magic_quotes_runtime(false);
}
@set_time_limit(0); // @ - can be disabled
@ini_set("precision", 15); // @ - can be disabled, 15 - internal PHP precision
@ini_set("precision", '15'); // @ - can be disabled, 15 - internal PHP precision
include "../adminer/include/lang.inc.php";
include "../adminer/lang/$LANG.inc.php";
include "../adminer/lang/" . LANG . ".inc.php";
include "../adminer/include/db.inc.php";
include "../adminer/include/pdo.inc.php";
include "../adminer/include/driver.inc.php";
include "../adminer/drivers/sqlite.inc.php";
@@ -80,18 +81,18 @@ include "./include/adminer.inc.php";
include "../adminer/include/plugins.inc.php";
if (function_exists('adminer_object')) {
$adminer = adminer_object();
Adminer::$instance = adminer_object();
} elseif (is_dir("adminer-plugins") || file_exists("adminer-plugins.php")) {
$adminer = new Plugins(null);
Adminer::$instance = new Plugins(null);
} else {
$adminer = new Adminer;
Adminer::$instance = new Adminer;
}
// this is matched by compile.php
include "../adminer/drivers/mysql.inc.php"; // must be included as last driver
define('Adminer\JUSH', Driver::$jush);
define('Adminer\SERVER', $_GET[DRIVER]); // read from pgsql=localhost
define('Adminer\SERVER', $_GET[DRIVER]); // read from pgsql=localhost, '' means default server, null means no server
define('Adminer\DB', $_GET["db"]); // for the sake of speed and size
define(
'Adminer\ME',

View File

@@ -10,7 +10,7 @@ if (isset($_GET["import"])) {
if (
!(DB != ""
? $connection->select_db(DB)
? connection()->select_db(DB)
: isset($_GET["sql"]) || isset($_GET["dump"]) || isset($_GET["database"]) || isset($_GET["processlist"]) || isset($_GET["privileges"]) || isset($_GET["user"]) || isset($_GET["variables"])
|| $_GET["script"] == "connect" || $_GET["script"] == "kill"
)
@@ -42,17 +42,17 @@ if (
echo "<a href='" . h(ME) . "$key='>$val</a>\n";
}
}
echo "<p>" . lang('%s version: %s through PHP extension %s', $drivers[DRIVER], "<b>" . h($connection->server_info) . "</b>", "<b>$connection->extension</b>") . "\n";
echo "<p>" . lang('%s version: %s through PHP extension %s', get_driver(DRIVER), "<b>" . h(connection()->server_info) . "</b>", "<b>" . connection()->extension . "</b>") . "\n";
echo "<p>" . lang('Logged as: %s', "<b>" . h(logged_user()) . "</b>") . "\n";
if (isset($adminer->plugins) && is_array($adminer->plugins)) {
if (isset(adminer()->plugins) && is_array(adminer()->plugins)) {
echo "<p>" . lang('Loaded plugins') . ":\n<ul>\n";
foreach ($adminer->plugins as $plugin) {
foreach (adminer()->plugins as $plugin) {
$reflection = new \ReflectionObject($plugin);
echo "<li><b>" . get_class($plugin) . "</b>" . h(preg_match('~^/[\s*]+(.+)~', $reflection->getDocComment(), $match) ? ": $match[1]" : "") . "\n";
}
echo "</ul>\n";
}
$databases = $adminer->databases();
$databases = adminer()->databases();
if ($databases) {
$scheme = support("scheme");
$collations = collations();

View File

@@ -3,12 +3,12 @@ namespace Adminer;
// coverage is used in tests and removed in compilation
if (extension_loaded("xdebug") && file_exists(sys_get_temp_dir() . "/adminer.coverage")) {
function save_coverage() {
function save_coverage(): void {
$coverage_filename = sys_get_temp_dir() . "/adminer.coverage";
$coverage = unserialize(file_get_contents($coverage_filename));
foreach (xdebug_get_code_coverage() as $filename => $lines) {
foreach ($lines as $l => $val) {
if (!$coverage[$filename][$l] || $val > 0) {
if (!idx($coverage[$filename], $l) || $val > 0) {
$coverage[$filename][$l] = $val;
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace Adminer;
// this could be interface when "Db extends \mysqli" can have compatible type declarations (PHP 7)
// interfaces can include properties only since PHP 8.4
abstract class SqlDb {
/** @var Db */ static $instance;
public string $extension; // extension name
public string $flavor = ''; // different vendor with the same API, e.g. MariaDB; usually stays empty
public string $server_info; // server version
public int $affected_rows = 0; // number of affected rows
public string $info = ''; // see https://php.net/mysql_info
public int $errno = 0; // last error code
public string $error = ''; // last error message
/** @var Result|bool */ protected $multi; // used for multiquery
/** Connect to server
* @return string error message
*/
abstract function attach(?string $server, string $username, string $password): string;
/** Quote string to use in SQL
* @return string escaped string enclosed in '
*/
abstract function quote(string $string): string;
/** Select database
* @return bool boolish
*/
abstract function select_db(string $database);
/** Send query
* @return Result|bool
*/
abstract function query(string $query, bool $unbuffered = false);
/** Send query with more resultsets
* @return Result|bool
*/
function multi_query(string $query) {
return $this->multi = $this->query($query);
}
/** Get current resultset
* @return Result|bool
*/
function store_result() {
return $this->multi;
}
/** Fetch next resultset */
function next_result(): bool {
return false;
}
}

View File

@@ -2,14 +2,11 @@
namespace Adminer;
/** Print HTML header
* @param string used in title, breadcrumb and heading, should be HTML escaped
* @param string
* @param mixed ["key" => "link", "key2" => ["link", "desc"]], null for nothing, false for driver only, true for driver and server
* @param string used after colon in title and heading, should be HTML escaped
* @return null
* @param string $title used in title, breadcrumb and heading, should be HTML escaped
* @param mixed $breadcrumb ["key" => "link", "key2" => ["link", "desc"]], null for nothing, false for driver only, true for driver and server
* @param string $title2 used after colon in title and heading, should be HTML escaped
*/
function page_header($title, $error = "", $breadcrumb = array(), $title2 = "") {
global $LANG, $VERSION, $adminer, $drivers;
function page_header(string $title, string $error = "", $breadcrumb = array(), string $title2 = ""): void {
page_headers();
if (is_ajax() && $error) {
page_messages($error);
@@ -19,18 +16,18 @@ function page_header($title, $error = "", $breadcrumb = array(), $title2 = "") {
ob_start(null, 4096);
}
$title_all = $title . ($title2 != "" ? ": $title2" : "");
$title_page = strip_tags($title_all . (SERVER != "" && SERVER != "localhost" ? h(" - " . SERVER) : "") . " - " . $adminer->name());
$title_page = strip_tags($title_all . (SERVER != "" && SERVER != "localhost" ? h(" - " . SERVER) : "") . " - " . adminer()->name());
// initial-scale=1 is the default but Chrome 134 on iOS is not able to zoom out without it
?>
<!DOCTYPE html>
<html lang="<?php echo $LANG; ?>" dir="<?php echo lang('ltr'); ?>">
<html lang="<?php echo LANG; ?>" dir="<?php echo lang('ltr'); ?>">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="robots" content="noindex">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title><?php echo $title_page; ?></title>
<link rel="stylesheet" href="../adminer/static/default.css">
<?php
$css = $adminer->css();
$css = adminer()->css();
$has_light = false;
$has_dark = false;
foreach ($css as $filename) {
@@ -53,7 +50,7 @@ function page_header($title, $error = "", $breadcrumb = array(), $title2 = "") {
// this is matched by compile.php
echo script_src("../adminer/static/functions.js");
echo script_src("static/editing.js");
if ($adminer->head($dark)) {
if (adminer()->head($dark)) {
echo "<link rel='shortcut icon' type='image/x-icon' href='../adminer/static/favicon.ico'>\n";
echo "<link rel='apple-touch-icon' href='../adminer/static/favicon.ico'>\n";
}
@@ -79,7 +76,7 @@ fQIDAQAB
}
}
echo script("mixin(document.body, {onkeydown: bodyKeydown, onclick: bodyClick"
. (isset($_COOKIE["adminer_version"]) ? "" : ", onload: partial(verifyVersion, '$VERSION', '" . js_escape(ME) . "', '" . get_token() . "')") // $token may be empty in auth.inc.php
. (isset($_COOKIE["adminer_version"]) ? "" : ", onload: partial(verifyVersion, '" . VERSION . "', '" . js_escape(ME) . "', '" . get_token() . "')")
. "});
document.body.classList.replace('nojs', 'js');
const offlineMessage = '" . js_escape(lang('You are offline.')) . "';
@@ -88,11 +85,12 @@ const thousandsSeparator = '" . js_escape(lang(',')) . "';")
echo "<div id='help' class='jush-" . JUSH . " jsonly hidden'></div>\n";
echo script("mixin(qs('#help'), {onmouseover: () => { helpOpen = 1; }, onmouseout: helpMouseout});");
echo "<div id='content'>\n";
echo "<span id='menuopen' class='jsonly'>" . icon("move", "", "menu", "") . "</span>" . script("qs('#menuopen').onclick = event => { qs('#foot').classList.toggle('foot'); event.stopPropagation(); }");
if ($breadcrumb !== null) {
$link = substr(preg_replace('~\b(username|db|ns)=[^&]*&~', '', ME), 0, -1);
echo '<p id="breadcrumb"><a href="' . h($link ?: ".") . '">' . $drivers[DRIVER] . '</a> » ';
echo '<p id="breadcrumb"><a href="' . h($link ?: ".") . '">' . get_driver(DRIVER) . '</a> » ';
$link = substr(preg_replace('~\b(db|ns)=[^&]*&~', '', ME), 0, -1);
$server = $adminer->serverName(SERVER);
$server = adminer()->serverName(SERVER);
$server = ($server != "" ? $server : lang('Server'));
if ($breadcrumb === false) {
echo "$server\n";
@@ -127,31 +125,28 @@ const thousandsSeparator = '" . js_escape(lang(',')) . "';")
define('Adminer\PAGE_HEADER', 1);
}
/** Send HTTP headers
* @return null
*/
function page_headers() {
global $adminer;
/** Send HTTP headers */
function page_headers(): void {
header("Content-Type: text/html; charset=utf-8");
header("Cache-Control: no-cache");
header("X-Frame-Options: deny"); // ClickJacking protection in IE8, Safari 4, Chrome 2, Firefox 3.6.9
header("X-XSS-Protection: 0"); // prevents introducing XSS in IE8 by removing safe parts of the page
header("X-Content-Type-Options: nosniff");
header("Referrer-Policy: origin-when-cross-origin");
foreach ($adminer->csp() as $csp) {
foreach (adminer()->csp(csp()) as $csp) {
$header = array();
foreach ($csp as $key => $val) {
$header[] = "$key $val";
}
header("Content-Security-Policy: " . implode("; ", $header));
}
$adminer->headers();
adminer()->headers();
}
/** Get Content Security Policy headers
* @return array of arrays with directive name in key, allowed sources in value
* @return list<string[]> of arrays with directive name in key, allowed sources in value
*/
function csp() {
function csp(): array {
return array(
array(
"script-src" => "'self' 'unsafe-inline' 'nonce-" . get_nonce() . "' 'strict-dynamic'", // 'self' is a fallback for browsers not supporting 'strict-dynamic', 'unsafe-inline' is a fallback for browsers not supporting 'nonce-'
@@ -167,7 +162,7 @@ function csp() {
/** Get a CSP nonce
* @return string Base64 value
*/
function get_nonce() {
function get_nonce(): string {
static $nonce;
if (!$nonce) {
$nonce = base64_encode(rand_string());
@@ -175,14 +170,10 @@ function get_nonce() {
return $nonce;
}
/** Print flash and error messages
* @param string
* @return null
*/
function page_messages($error) {
global $adminer;
/** Print flash and error messages */
function page_messages(string $error): void {
$uri = preg_replace('~^[^?]*~', '', $_SERVER["REQUEST_URI"]);
$messages = $_SESSION["messages"][$uri];
$messages = idx($_SESSION["messages"], $uri);
if ($messages) {
echo "<div class='message'>" . implode("</div>\n<div class='message'>", $messages) . "</div>" . script("messagesPrint();");
unset($_SESSION["messages"][$uri]);
@@ -190,20 +181,18 @@ function page_messages($error) {
if ($error) {
echo "<div class='error'>$error</div>\n";
}
if ($adminer->error) { // separate <div>
echo "<div class='error'>$adminer->error</div>\n";
if (adminer()->error) { // separate <div>
echo "<div class='error'>" . adminer()->error . "</div>\n";
}
}
/** Print HTML footer
* @param string "auth", "db", "ns"
* @return null
* @param ''|'auth'|'db'|'ns' $missing
*/
function page_footer($missing = "") {
global $adminer;
echo "</div>\n\n<div id='menu'>\n";
$adminer->navigation($missing);
echo "</div>\n\n";
function page_footer(string $missing = ""): void {
echo "</div>\n\n<div id='foot' class='foot'>\n<div id='menu'>\n";
adminer()->navigation($missing);
echo "</div>\n";
if ($missing != "auth") {
?>
<form action="" method="post">
@@ -211,9 +200,9 @@ function page_footer($missing = "") {
<span><?php echo h($_GET["username"]) . "\n"; ?></span>
<input type="submit" name="logout" value="<?php echo lang('Logout'); ?>" id="logout">
<?php echo input_token(); ?>
</p>
</form>
<?php
}
echo "</div>\n\n";
echo script("setupSubmitHighlight(document);");
}

View File

@@ -1,98 +1,94 @@
<?php
namespace Adminer;
$drivers = array();
/** Add a driver
* @param string
* @param string
* @return null
*/
function add_driver($id, $name) {
global $drivers;
$drivers[$id] = $name;
/** Add or overwrite a driver */
function add_driver(string $id, string $name): void {
SqlDriver::$drivers[$id] = $name;
}
/** Get driver name
* @param string
* @return string
*/
function get_driver($id) {
global $drivers;
return $drivers[$id];
/** Get driver name */
function get_driver(string $id): string {
return SqlDriver::$drivers[$id];
}
abstract class SqlDriver {
static $possibleDrivers = array();
static $jush; ///< @var string JUSH identifier
static Driver $instance;
/** @var string[] */ static array $drivers = array(); // all available drivers
/** @var list<string> */ static array $extensions = array(); // possible extensions in the current driver
static string $jush; // JUSH identifier
protected $conn;
protected $types = array(); ///< @var array [$description => [$type => $maximum_unsigned_length, ...], ...]
public $editFunctions = array(); ///< @var array of ["$type|$type2" => "$function/$function2"] functions used in editing, [0] - edit and insert, [1] - edit only
public $unsigned = array(); ///< @var array number variants
public $operators = array(); ///< @var array operators used in select
public $functions = array(); ///< @var array functions used in select
public $grouping = array(); ///< @var array grouping functions used in select
public $onActions = "RESTRICT|NO ACTION|CASCADE|SET NULL|SET DEFAULT"; ///< @var string used in foreign_keys()
public $inout = "IN|OUT|INOUT"; ///< @var string used in routines
public $enumLength = "'(?:''|[^'\\\\]|\\\\.)*'"; ///< @var string regular expression for parsing enum lengths
public $generated = array(); ///< @var array allowed types of generated columns
protected Db $conn;
/** @var int[][] */ protected array $types = array(); // [$group => [$type => $maximum_unsigned_length, ...], ...]
/** @var string[] */ public array $insertFunctions = array(); // ["$type|$type2" => "$function/$function2"] functions used in edit and insert
/** @var string[] */ public array $editFunctions = array(); // ["$type|$type2" => "$function/$function2"] functions used in edit only
/** @var list<string> */ public array $unsigned = array(); // number variants
/** @var list<string> */ public array $operators = array(); // operators used in select
/** @var list<string> */ public array $functions = array(); // functions used in select
/** @var list<string> */ public array $grouping = array(); // grouping functions used in select
public string $onActions = "RESTRICT|NO ACTION|CASCADE|SET NULL|SET DEFAULT"; // used in foreign_keys()
public string $inout = "IN|OUT|INOUT"; // used in routines
public string $enumLength = "'(?:''|[^'\\\\]|\\\\.)*'"; // regular expression for parsing enum lengths
/** @var list<string> */ public array $generated = array(); // allowed types of generated columns
/** Create object for performing database operations
* @param Db
/** Connect to the database
* @return Db|string string for error
*/
function __construct($connection) {
static function connect(?string $server, string $username, string $password) {
$connection = new Db;
return ($connection->attach($server, $username, $password) ?: $connection);
}
/** Create object for performing database operations */
function __construct(Db $connection) {
$this->conn = $connection;
}
/** Get all types
* @return array [$type => $maximum_unsigned_length, ...]
* @return int[] [$type => $maximum_unsigned_length, ...]
*/
function types() {
function types(): array {
return call_user_func_array('array_merge', array_values($this->types));
}
/** Get structured types
* @return array [$description => [$type, ...], ...]
* @return list<string>[]|list<string> [$description => [$type, ...], ...]
*/
function structuredTypes() {
function structuredTypes(): array {
return array_map('array_keys', $this->types);
}
/** Get enum values
* @param array
* @return string or null
* @param Field $field
* @return string|void
*/
function enumLength($field) {
function enumLength(array $field) {
}
/** Function used to convert the value inputted by user
* @param array
* @return string or null
* @param Field $field
* @return string|void
*/
function unconvertFunction($field) {
function unconvertFunction(array $field) {
}
/** Select data from table
* @param string
* @param array result of $adminer->selectColumnsProcess()[0]
* @param array result of $adminer->selectSearchProcess()
* @param array result of $adminer->selectColumnsProcess()[1]
* @param array result of $adminer->selectOrderProcess()
* @param int result of $adminer->selectLimitProcess()
* @param int index of page starting at zero
* @param bool whether to print the query
* @return Result
* @param list<string> $select result of adminer()->selectColumnsProcess()[0]
* @param list<string> $where result of adminer()->selectSearchProcess()
* @param list<string> $group result of adminer()->selectColumnsProcess()[1]
* @param list<string> $order result of adminer()->selectOrderProcess()
* @param int $limit result of adminer()->selectLimitProcess()
* @param int $page index of page starting at zero
* @param bool $print whether to print the query
* @return Result|false
*/
function select($table, $select, $where, $group, $order = array(), $limit = 1, $page = 0, $print = false) {
global $adminer;
function select(string $table, array $select, array $where, array $group, array $order = array(), int $limit = 1, ?int $page = 0, bool $print = false) {
$is_group = (count($group) < count($select));
$query = $adminer->selectQueryBuild($select, $where, $group, $order, $limit, $page);
$query = adminer()->selectQueryBuild($select, $where, $group, $order, $limit, $page);
if (!$query) {
$query = "SELECT" . limit(
($_GET["page"] != "last" && $limit != "" && $group && $is_group && JUSH == "sql" ? "SQL_CALC_FOUND_ROWS " : "") . implode(", ", $select) . "\nFROM " . table($table),
($_GET["page"] != "last" && $limit && $group && $is_group && JUSH == "sql" ? "SQL_CALC_FOUND_ROWS " : "") . implode(", ", $select) . "\nFROM " . table($table),
($where ? "\nWHERE " . implode(" AND ", $where) : "") . ($group && $is_group ? "\nGROUP BY " . implode(", ", $group) : "") . ($order ? "\nORDER BY " . implode(", ", $order) : ""),
($limit != "" ? +$limit : null),
$limit,
($page ? $limit * $page : 0),
"\n"
);
@@ -100,31 +96,28 @@ abstract class SqlDriver {
$start = microtime(true);
$return = $this->conn->query($query);
if ($print) {
echo $adminer->selectQuery($query, $start, !$return);
echo adminer()->selectQuery($query, $start, !$return);
}
return $return;
}
/** Delete data from table
* @param string
* @param string " WHERE ..."
* @param int 0 or 1
* @return bool
* @param string $queryWhere " WHERE ..."
* @param int $limit 0 or 1
* @return Result|bool
*/
function delete($table, $queryWhere, $limit = 0) {
function delete(string $table, string $queryWhere, int $limit = 0) {
$query = "FROM " . table($table);
return queries("DELETE" . ($limit ? limit1($table, $query, $queryWhere) : " $query$queryWhere"));
}
/** Update data in table
* @param string
* @param array escaped columns in keys, quoted data in values
* @param string " WHERE ..."
* @param int 0 or 1
* @param string
* @return bool
* @param string[] $set escaped columns in keys, quoted data in values
* @param string $queryWhere " WHERE ..."
* @param int $limit 0 or 1
* @return Result|bool
*/
function update($table, $set, $queryWhere, $limit = 0, $separator = "\n") {
function update(string $table, array $set, string $queryWhere, int $limit = 0, string $separator = "\n") {
$values = array();
foreach ($set as $key => $val) {
$values[] = "$key = $val";
@@ -134,150 +127,143 @@ abstract class SqlDriver {
}
/** Insert data into table
* @param string
* @param array escaped columns in keys, quoted data in values
* @return bool
* @param string[] $set escaped columns in keys, quoted data in values
* @return Result|bool
*/
function insert($table, $set) {
function insert(string $table, array $set) {
return queries("INSERT INTO " . table($table) . ($set
? " (" . implode(", ", array_keys($set)) . ")\nVALUES (" . implode(", ", $set) . ")"
: " DEFAULT VALUES"
) . $this->insertReturning($table));
}
/** Get RETURNING clause for INSERT queries, PostgreSQL specific
* @param string
* @return string
*/
function insertReturning($table) {
/** Get RETURNING clause for INSERT queries (PostgreSQL specific) */
function insertReturning(string $table): string {
return "";
}
/** Insert or update data in table
* @param string
* @param array
* @param array of arrays with escaped columns in keys and quoted data in values
* @return bool
* @param list<string[]> $rows of arrays with escaped columns in keys and quoted data in values
* @param int[] $primary column names in keys
* @return Result|bool
*/
function insertUpdate($table, $rows, $primary) {
function insertUpdate(string $table, array $rows, array $primary) {
return false;
}
/** Begin transaction
* @return bool
* @return Result|bool
*/
function begin() {
return queries("BEGIN");
}
/** Commit transaction
* @return bool
* @return Result|bool
*/
function commit() {
return queries("COMMIT");
}
/** Rollback transaction
* @return bool
* @return Result|bool
*/
function rollback() {
return queries("ROLLBACK");
}
/** Return query with a timeout
* @param string
* @param int seconds
* @return string or null if the driver doesn't support query timeouts
* @param int $timeout seconds
* @return string|void null if the driver doesn't support query timeouts
*/
function slowQuery($query, $timeout) {
function slowQuery(string $query, int $timeout) {
}
/** Convert column to be searchable
* @param string escaped column name
* @param array ["op" => , "val" => ]
* @param array
* @return string
* @param string $idf escaped column name
* @param array{op:string, val:string} $val
* @param Field $field
*/
function convertSearch($idf, $val, $field) {
function convertSearch(string $idf, array $val, array $field): string {
return $idf;
}
/** Convert operator so it can be used in search
* @param string $operator
* @return string
*/
function convertOperator($operator) {
/** Convert operator so it can be used in search */
function convertOperator(string $operator): string {
return $operator;
}
/** Convert value returned by database to actual value
* @param string
* @param array
* @return string
* @param Field $field
*/
function value($val, $field) {
function value(?string $val, array $field): ?string {
return (method_exists($this->conn, 'value')
? $this->conn->value($val, $field)
: (is_resource($val) ? stream_get_contents($val) : $val)
);
}
/** Quote binary string
* @param string
* @return string
*/
function quoteBinary($s) {
/** Quote binary string */
function quoteBinary(string $s): string {
return q($s);
}
/** Get warnings about the last command
* @return string HTML
* @return string|void HTML
*/
function warnings() {
return '';
}
/** Get help link for table
* @param string
* @param bool
* @return string relative URL or null
* @return string|void relative URL
*/
function tableHelp($name, $is_view = false) {
function tableHelp(string $name, bool $is_view = false) {
}
/** Check if C-style escapes are supported
* @return bool
*/
function hasCStyleEscapes() {
/** Check if C-style escapes are supported */
function hasCStyleEscapes(): bool {
return false;
}
/** Get supported engines
* @return array
* @return list<string>
*/
function engines() {
function engines(): array {
return array();
}
/** Check whether table supports indexes
* @param array result of table_status()
* @return bool
* @param TableStatus $table_status
*/
function supportsIndex($table_status) {
function supportsIndex(array $table_status): bool {
return !is_view($table_status);
}
/** Get defined check constraints
* @param string
* @return array [$name => $clause]
* @return string[] [$name => $clause]
*/
function checkConstraints($table) {
function checkConstraints(string $table): array {
// MariaDB contains CHECK_CONSTRAINTS.TABLE_NAME, MySQL and PostrgreSQL not
return get_key_vals("SELECT c.CONSTRAINT_NAME, CHECK_CLAUSE
FROM INFORMATION_SCHEMA.CHECK_CONSTRAINTS c
JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS t ON c.CONSTRAINT_SCHEMA = t.CONSTRAINT_SCHEMA AND c.CONSTRAINT_NAME = t.CONSTRAINT_NAME
WHERE c.CONSTRAINT_SCHEMA = " . q($_GET["ns"] != "" ? $_GET["ns"] : DB) . "
AND t.TABLE_NAME = " . q($table) . "
AND CHECK_CLAUSE NOT LIKE '% IS NOT NULL'"); // ignore default IS NOT NULL checks in PostrgreSQL
AND CHECK_CLAUSE NOT LIKE '% IS NOT NULL'", $this->conn); // ignore default IS NOT NULL checks in PostrgreSQL
}
/** Get all fields in the current schema
* @return array<list<array{field:string, null:bool, type:string, length:?numeric-string, primary?:numeric-string}>>
*/
function allFields(): array {
$return = array();
foreach (get_rows("SELECT TABLE_NAME AS tab, COLUMN_NAME AS field, IS_NULLABLE AS nullable, DATA_TYPE AS type, CHARACTER_MAXIMUM_LENGTH AS length" . (JUSH == 'sql' ? ", COLUMN_KEY = 'PRI' AS `primary`" : "") . "
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = " . q($_GET["ns"] != "" ? $_GET["ns"] : DB) . "
ORDER BY TABLE_NAME, ORDINAL_POSITION", $this->conn) as $row) {
$row["null"] = ($row["nullable"] == "YES");
$return[$row["tab"]][] = $row;
}
return $return;
}
}

View File

@@ -4,13 +4,12 @@ namespace Adminer;
// This file is not used in Adminer Editor.
/** Print select result
* @param Result
* @param Db connection to examine indexes
* @param array
* @param int
* @return array $orgtables
* @param Result $result
* @param string[] $orgtables
* @param int|numeric-string $limit
* @return string[] $orgtables
*/
function select($result, $connection2 = null, $orgtables = array(), $limit = 0) {
function print_select_result($result, ?Db $connection2 = null, array $orgtables = array(), $limit = 0): array {
$links = array(); // colno => orgtable - create links from these columns
$indexes = array(); // orgtable => array(column => colno) - primary keys
$columns = array(); // orgtable => array(column => ) - not selected columns in primary key
@@ -101,10 +100,9 @@ function select($result, $connection2 = null, $orgtables = array(), $limit = 0)
}
/** Get referencable tables with single column primary key except self
* @param string
* @return array [$table_name => $field]
* @return array<string, Field> [$table_name => $field]
*/
function referencable_primary($self) {
function referencable_primary(string $self): array {
$return = array(); // table_name => field
foreach (table_status('', true) as $table_name => $table) {
if ($table_name != $self && fk_support($table)) {
@@ -123,13 +121,9 @@ function referencable_primary($self) {
}
/** Print SQL <textarea> tag
* @param string
* @param string or array in which case [0] of every element is used
* @param int
* @param int
* @return null
* @param string|list<array{string}> $value
*/
function textarea($name, $value, $rows = 10, $cols = 80) {
function textarea(string $name, $value, int $rows = 10, int $cols = 80): void {
echo "<textarea name='" . h($name) . "' rows='$rows' cols='$cols' class='sqlarea jush-" . JUSH . "' spellcheck='false' wrap='off'>";
if (is_array($value)) {
foreach ($value as $val) { // not implode() to save memory
@@ -142,14 +136,9 @@ function textarea($name, $value, $rows = 10, $cols = 80) {
}
/** Generate HTML <select> or <input> if $options are empty
* @param string
* @param array
* @param string
* @param string
* @param string
* @return string
* @param string[] $options
*/
function select_input($attrs, $options, $value = "", $onchange = "", $placeholder = "") {
function select_input(string $attrs, array $options, ?string $value = "", string $onchange = "", string $placeholder = ""): string {
$tag = ($options ? "select" : "input");
return "<$tag$attrs" . ($options
? "><option value=''>$placeholder" . optionlist($options, $value, true) . "</select>"
@@ -158,11 +147,10 @@ function select_input($attrs, $options, $value = "", $onchange = "", $placeholde
}
/** Print one row in JSON object
* @param string or "" to close the object
* @param string
* @return null
* @param string $key or "" to close the object
* @param string|int $val
*/
function json_row($key, $val = null) {
function json_row(string $key, $val = null): void {
static $first = true;
if ($first) {
echo "{";
@@ -177,21 +165,18 @@ function json_row($key, $val = null) {
}
/** Print table columns for type edit
* @param string
* @param array
* @param array
* @param array returned by referencable_primary()
* @param array extra types to prepend
* @return null
* @param Field $field
* @param list<string> $collations
* @param string[] $foreign_keys
* @param list<string> $extra_types extra types to prepend
*/
function edit_type($key, $field, $collations, $foreign_keys = array(), $extra_types = array()) {
global $driver;
function edit_type(string $key, array $field, array $collations, array $foreign_keys = array(), array $extra_types = array()): void {
$type = $field["type"];
echo "<td><select name='" . h($key) . "[type]' class='type' aria-labelledby='label-type'>";
if ($type && !array_key_exists($type, $driver->types()) && !isset($foreign_keys[$type]) && !in_array($type, $extra_types)) {
if ($type && !array_key_exists($type, driver()->types()) && !isset($foreign_keys[$type]) && !in_array($type, $extra_types)) {
$extra_types[] = $type;
}
$structured_types = $driver->structuredTypes();
$structured_types = driver()->structuredTypes();
if ($foreign_keys) {
$structured_types[lang('Foreign keys')] = $foreign_keys;
}
@@ -205,25 +190,23 @@ function edit_type($key, $field, $collations, $foreign_keys = array(), $extra_ty
? "<input list='collations' name='" . h($key) . "[collation]'" . (preg_match('~(char|text|enum|set)$~', $type) ? "" : " class='hidden'") . " value='" . h($field["collation"]) . "' placeholder='(" . lang('collation') . ")'>"
: ''
);
echo ($driver->unsigned ? "<select name='" . h($key) . "[unsigned]'" . (!$type || preg_match(number_type(), $type) ? "" : " class='hidden'") . '><option>' . optionlist($driver->unsigned, $field["unsigned"]) . '</select>' : '');
echo (driver()->unsigned ? "<select name='" . h($key) . "[unsigned]'" . (!$type || preg_match(number_type(), $type) ? "" : " class='hidden'") . '><option>' . optionlist(driver()->unsigned, $field["unsigned"]) . '</select>' : '');
echo (isset($field['on_update']) ? "<select name='" . h($key) . "[on_update]'" . (preg_match('~timestamp|datetime~', $type) ? "" : " class='hidden'") . '>'
. optionlist(array("" => "(" . lang('ON UPDATE') . ")", "CURRENT_TIMESTAMP"), (preg_match('~^CURRENT_TIMESTAMP~i', $field["on_update"]) ? "CURRENT_TIMESTAMP" : $field["on_update"]))
. '</select>' : ''
);
echo ($foreign_keys
? "<select name='" . h($key) . "[on_delete]'" . (preg_match("~`~", $type) ? "" : " class='hidden'") . "><option value=''>(" . lang('ON DELETE') . ")" . optionlist(explode("|", $driver->onActions), $field["on_delete"]) . "</select> "
? "<select name='" . h($key) . "[on_delete]'" . (preg_match("~`~", $type) ? "" : " class='hidden'") . "><option value=''>(" . lang('ON DELETE') . ")" . optionlist(explode("|", driver()->onActions), $field["on_delete"]) . "</select> "
: " " // space for IE
);
}
/** Get partition info
* @param string
* @return array
* @return array{partition_by:string, partition:string, partitions:string, partition_names:list<string>, partition_values:list<string>}
*/
function get_partitions_info($table) {
global $connection;
function get_partitions_info(string $table): array {
$from = "FROM information_schema.PARTITIONS WHERE TABLE_SCHEMA = " . q(DB) . " AND TABLE_NAME = " . q($table);
$result = $connection->query("SELECT PARTITION_METHOD, PARTITION_EXPRESSION, PARTITION_ORDINAL_POSITION $from ORDER BY PARTITION_ORDINAL_POSITION DESC LIMIT 1");
$result = connection()->query("SELECT PARTITION_METHOD, PARTITION_EXPRESSION, PARTITION_ORDINAL_POSITION $from ORDER BY PARTITION_ORDINAL_POSITION DESC LIMIT 1");
$return = array();
list($return["partition_by"], $return["partition"], $return["partitions"]) = $result->fetch_row();
$partitions = get_key_vals("SELECT PARTITION_NAME, PARTITION_DESCRIPTION $from AND PARTITION_NAME != '' ORDER BY PARTITION_ORDINAL_POSITION");
@@ -232,13 +215,9 @@ function get_partitions_info($table) {
return $return;
}
/** Filter length value including enums
* @param string
* @return string
*/
function process_length($length) {
global $driver;
$enum_length = $driver->enumLength;
/** Filter length value including enums */
function process_length(?string $length): string {
$enum_length = driver()->enumLength;
return (preg_match("~^\\s*\\(?\\s*$enum_length(?:\\s*,\\s*$enum_length)*+\\s*\\)?\\s*\$~", $length) && preg_match_all("~$enum_length~", $length, $matches)
? "(" . implode(",", $matches[0]) . ")"
: preg_replace('~^[0-9].*~', '(\0)', preg_replace('~[^-0-9,+()[\]]~', '', $length))
@@ -246,25 +225,22 @@ function process_length($length) {
}
/** Create SQL string from field type
* @param array
* @param string
* @return string
* @param FieldType $field
*/
function process_type($field, $collate = "COLLATE") {
global $driver;
function process_type(array $field, string $collate = "COLLATE"): string {
return " $field[type]"
. process_length($field["length"])
. (preg_match(number_type(), $field["type"]) && in_array($field["unsigned"], $driver->unsigned) ? " $field[unsigned]" : "")
. (preg_match(number_type(), $field["type"]) && in_array($field["unsigned"], driver()->unsigned) ? " $field[unsigned]" : "")
. (preg_match('~char|text|enum|set~', $field["type"]) && $field["collation"] ? " $collate " . (JUSH == "mssql" ? $field["collation"] : q($field["collation"])) : "")
;
}
/** Create SQL string from field
* @param array basic field information
* @param array information about field type
* @return array ["field", "type", "NULL", "DEFAULT", "ON UPDATE", "COMMENT", "AUTO_INCREMENT"]
* @param Field $field basic field information
* @param Field $type_field information about field type
* @return list<string> ["field", "type", "NULL", "DEFAULT", "ON UPDATE", "COMMENT", "AUTO_INCREMENT"]
*/
function process_field($field, $type_field) {
function process_field(array $field, array $type_field): array {
// MariaDB exports CURRENT_TIMESTAMP as a function.
if ($field["on_update"]) {
$field["on_update"] = str_ireplace("current_timestamp()", "CURRENT_TIMESTAMP", $field["on_update"]);
@@ -281,14 +257,12 @@ function process_field($field, $type_field) {
}
/** Get default value clause
* @param array
* @return string
* @param Field $field
*/
function default_value($field) {
global $driver;
function default_value(array $field): string {
$default = $field["default"];
$generated = $field["generated"];
return ($default === null ? "" : (in_array($generated, $driver->generated)
return ($default === null ? "" : (in_array($generated, driver()->generated)
? (JUSH == "mssql" ? " AS ($default)" . ($generated == "VIRTUAL" ? "" : " $generated") . "" : " GENERATED ALWAYS AS ($default) $generated")
: " DEFAULT " . (!preg_match('~^GENERATED ~i', $default) && (preg_match('~char|binary|text|json|enum|set~', $field["type"]) || preg_match('~^(?![a-z])~i', $default))
? (JUSH == "sql" && preg_match('~text|json~', $field["type"]) ? "(" . q($default) . ")" : q($default)) // MySQL requires () around default value of text column
@@ -298,10 +272,9 @@ function default_value($field) {
}
/** Get type class to use in CSS
* @param string
* @return string class=''
* @return string|void class=''
*/
function type_class($type) {
function type_class(string $type) {
foreach (
array(
'char' => 'text',
@@ -317,14 +290,12 @@ function type_class($type) {
}
/** Print table interior for fields editing
* @param array
* @param array
* @param string TABLE or PROCEDURE
* @param array returned by referencable_primary()
* @return null
* @param (Field|RoutineField)[] $fields
* @param list<string> $collations
* @param 'TABLE'|'PROCEDURE' $type
* @param string[] $foreign_keys
*/
function edit_fields($fields, $collations, $type = "TABLE", $foreign_keys = array()) {
global $driver;
function edit_fields(array $fields, array $collations, $type = "TABLE", array $foreign_keys = array()): void {
$fields = array_values($fields);
$default_class = (($_POST ? $_POST["defaults"] : get_setting("defaults")) ? "" : " class='hidden'");
$comment_class = (($_POST ? $_POST["comments"] : get_setting("comments")) ? "" : " class='hidden'");
@@ -347,15 +318,15 @@ function edit_fields($fields, $collations, $type = "TABLE", $foreign_keys = arra
echo "<td id='label-default'$default_class>" . lang('Default value');
echo (support("comment") ? "<td id='label-comment'$comment_class>" . lang('Comment') : "");
}
echo "<td><input type='image' class='icon' name='add[" . (support("move_col") ? 0 : count($fields)) . "]' src='../adminer/static/plus.gif' alt='+' title='" . lang('Add next') . "'>" . script("row_count = " . count($fields) . ";");
echo "<td>" . icon("plus", "add[" . (support("move_col") ? 0 : count($fields)) . "]", "+", lang('Add next'));
echo "</thead>\n<tbody>\n";
echo script("mixin(qsl('tbody'), {onclick: editingClick, onkeydown: editingKeydown, oninput: editingInput});");
foreach ($fields as $i => $field) {
$i++;
$orig = $field[($_POST ? "orig" : "field")];
$display = (isset($_POST["add"][$i-1]) || (isset($field["field"]) && !$_POST["drop_col"][$i])) && (support("drop_col") || $orig == "");
$display = (isset($_POST["add"][$i-1]) || (isset($field["field"]) && !idx($_POST["drop_col"], $i))) && (support("drop_col") || $orig == "");
echo "<tr" . ($display ? "" : " style='display: none;'") . ">\n";
echo ($type == "PROCEDURE" ? "<td>" . html_select("fields[$i][inout]", explode("|", $driver->inout), $field["inout"]) : "") . "<th>";
echo ($type == "PROCEDURE" ? "<td>" . html_select("fields[$i][inout]", explode("|", driver()->inout), $field["inout"]) : "") . "<th>";
if ($display) {
echo "<input name='fields[$i][field]' value='" . h($field["field"]) . "' data-maxlength='64' autocapitalize='off' aria-labelledby='label-name'>";
}
@@ -364,8 +335,8 @@ function edit_fields($fields, $collations, $type = "TABLE", $foreign_keys = arra
if ($type == "TABLE") {
echo "<td>" . checkbox("fields[$i][null]", 1, $field["null"], "", "", "block", "label-null");
echo "<td><label class='block'><input type='radio' name='auto_increment_col' value='$i'" . ($field["auto_increment"] ? " checked" : "") . " aria-labelledby='label-ai'></label>";
echo "<td$default_class>" . ($driver->generated
? html_select("fields[$i][generated]", array_merge(array("", "DEFAULT"), $driver->generated), $field["generated"]) . " "
echo "<td$default_class>" . (driver()->generated
? html_select("fields[$i][generated]", array_merge(array("", "DEFAULT"), driver()->generated), $field["generated"]) . " "
: checkbox("fields[$i][generated]", 1, $field["generated"], "", "", "", "label-default")
);
echo "<input name='fields[$i][default]' value='" . h($field["default"]) . "' aria-labelledby='label-default'>";
@@ -373,19 +344,18 @@ function edit_fields($fields, $collations, $type = "TABLE", $foreign_keys = arra
}
echo "<td>";
echo (support("move_col") ?
"<input type='image' class='icon' name='add[$i]' src='../adminer/static/plus.gif' alt='+' title='" . lang('Add next') . "'> "
. "<input type='image' class='icon' name='up[$i]' src='../adminer/static/up.gif' alt='↑' title='" . lang('Move up') . "'> "
. "<input type='image' class='icon' name='down[$i]' src='../adminer/static/down.gif' alt='↓' title='" . lang('Move down') . "'> "
icon("plus", "add[$i]", "+", lang('Add next')) . " "
. icon("up", "up[$i]", "", lang('Move up')) . " "
. icon("down", "down[$i]", "", lang('Move down')) . " "
: "");
echo ($orig == "" || support("drop_col") ? "<input type='image' class='icon' name='drop_col[$i]' src='../adminer/static/cross.gif' alt='x' title='" . lang('Remove') . "'>" : "");
echo ($orig == "" || support("drop_col") ? icon("cross", "drop_col[$i]", "x", lang('Remove')) : "");
}
}
/** Move fields up and down or add field
* @param array
* @return bool
* @param Field[] $fields
*/
function process_fields(&$fields) {
function process_fields(array &$fields): bool {
$offset = 0;
if ($_POST["up"]) {
$last = 0;
@@ -423,21 +393,19 @@ function process_fields(&$fields) {
}
/** Callback used in routine()
* @param array
* @return string
* @param list<string> $match
*/
function normalize_enum($match) {
return "'" . str_replace("'", "''", addcslashes(stripcslashes(str_replace($match[0][0] . $match[0][0], $match[0][0], substr($match[0], 1, -1))), '\\')) . "'";
function normalize_enum(array $match): string {
$val = $match[0];
return "'" . str_replace("'", "''", addcslashes(stripcslashes(str_replace($val[0] . $val[0], $val[0], substr($val, 1, -1))), '\\')) . "'";
}
/** Issue grant or revoke commands
* @param string GRANT or REVOKE
* @param array
* @param string
* @param string
* @return bool
* @param 'GRANT'|'REVOKE' $grant
* @param list<string> $privileges
* @return Result|bool
*/
function grant($grant, $privileges, $columns, $on) {
function grant(string $grant, array $privileges, ?string $columns, string $on) {
if (!$privileges) {
return true;
}
@@ -452,20 +420,14 @@ function grant($grant, $privileges, $columns, $on) {
}
/** Drop old object and create a new one
* @param string drop old object query
* @param string create new object query
* @param string drop new object query
* @param string create test object query
* @param string drop test object query
* @param string
* @param string
* @param string
* @param string
* @param string
* @param string
* @return null redirect in success
* @param string $drop drop old object query
* @param string $create create new object query
* @param string $drop_created drop new object query
* @param string $test create test object query
* @param string $drop_test drop test object query
* @return void redirect on success
*/
function drop_create($drop, $create, $drop_created, $test, $drop_test, $location, $message_drop, $message_alter, $message_create, $old_name, $new_name) {
function drop_create(string $drop, string $create, string $drop_created, string $test, string $drop_test, string $location, string $message_drop, string $message_alter, string $message_create, string $old_name, string $new_name): void {
if ($_POST["drop"]) {
query_redirect($drop, $location, $message_drop);
} elseif ($old_name == "") {
@@ -486,11 +448,9 @@ function drop_create($drop, $create, $drop_created, $test, $drop_test, $location
}
/** Generate SQL query for creating trigger
* @param string
* @param array result of trigger()
* @return string
* @param Trigger $row
*/
function create_trigger($on, $row) {
function create_trigger(string $on, array $row): string {
$timing_event = " $row[Timing] $row[Event]" . (preg_match('~ OF~', $row["Event"]) ? " $row[Of]" : ""); // SQL injection
return "CREATE TRIGGER "
. idf_escape($row["Trigger"])
@@ -501,18 +461,16 @@ function create_trigger($on, $row) {
}
/** Generate SQL query for creating routine
* @param string "PROCEDURE" or "FUNCTION"
* @param array result of routine()
* @return string
* @param 'PROCEDURE'|'FUNCTION' $routine
* @param Routine $row
*/
function create_routine($routine, $row) {
global $driver;
function create_routine($routine, array $row): string {
$set = array();
$fields = (array) $row["fields"];
ksort($fields); // enforce fields order
foreach ($fields as $field) {
if ($field["field"] != "") {
$set[] = (preg_match("~^($driver->inout)\$~", $field["inout"]) ? "$field[inout] " : "") . idf_escape($field["field"]) . process_type($field, "CHARACTER SET");
$set[] = (preg_match("~^(driver()->inout)\$~", $field["inout"]) ? "$field[inout] " : "") . idf_escape($field["field"]) . process_type($field, "CHARACTER SET");
}
}
$definition = rtrim($row["definition"], ";");
@@ -525,20 +483,15 @@ function create_routine($routine, $row) {
;
}
/** Remove current user definer from SQL command
* @param string
* @return string
*/
function remove_definer($query) {
/** Remove current user definer from SQL command */
function remove_definer(string $query): string {
return preg_replace('~^([A-Z =]+) DEFINER=`' . preg_replace('~@(.*)~', '`@`(%|\1)', logged_user()) . '`~', '\1', $query); //! proper escaping of user
}
/** Format foreign key to use in SQL query
* @param array ["db" => string, "ns" => string, "table" => string, "source" => array, "target" => array, "on_delete" => one of $on_actions, "on_update" => one of $on_actions]
* @return string
* @param ForeignKey $foreign_key
*/
function format_foreign_key($foreign_key) {
global $driver;
function format_foreign_key(array $foreign_key): string {
$db = $foreign_key["db"];
$ns = $foreign_key["ns"];
return " FOREIGN KEY (" . implode(", ", array_map('Adminer\idf_escape', $foreign_key["source"])) . ") REFERENCES "
@@ -546,17 +499,16 @@ function format_foreign_key($foreign_key) {
. ($ns != "" && $ns != $_GET["ns"] ? idf_escape($ns) . "." : "")
. idf_escape($foreign_key["table"])
. " (" . implode(", ", array_map('Adminer\idf_escape', $foreign_key["target"])) . ")" //! reuse $name - check in older MySQL versions
. (preg_match("~^($driver->onActions)\$~", $foreign_key["on_delete"]) ? " ON DELETE $foreign_key[on_delete]" : "")
. (preg_match("~^($driver->onActions)\$~", $foreign_key["on_update"]) ? " ON UPDATE $foreign_key[on_update]" : "")
. (preg_match("~^(driver()->onActions)\$~", $foreign_key["on_delete"]) ? " ON DELETE $foreign_key[on_delete]" : "")
. (preg_match("~^(driver()->onActions)\$~", $foreign_key["on_update"]) ? " ON UPDATE $foreign_key[on_update]" : "")
;
}
/** Add a file to TAR
* @param string
* @param TmpFile
* @return null prints the output
* @param TmpFile $tmp_file
* @return void prints the output
*/
function tar_file($filename, $tmp_file) {
function tar_file(string $filename, $tmp_file): void {
$return = pack("a100a8a8a8a12a12", $filename, 644, 0, 0, decoct($tmp_file->size), decoct(time()));
$checksum = 8*32; // space for checksum itself
for ($i=0; $i < strlen($return); $i++) {
@@ -569,11 +521,8 @@ function tar_file($filename, $tmp_file) {
echo str_repeat("\0", 511 - ($tmp_file->size + 511) % 512);
}
/** Get INI bytes value
* @param string
* @return int
*/
function ini_bytes($ini) {
/** Get INI bytes value */
function ini_bytes(string $ini): int {
$val = ini_get($ini);
switch (strtolower(substr($val, -1))) {
case 'g':
@@ -587,22 +536,21 @@ function ini_bytes($ini) {
}
/** Create link to database documentation
* @param array JUSH => $path
* @param string HTML code
* @param string[] $paths JUSH => $path
* @param string $text HTML code
* @return string HTML code
*/
function doc_link($paths, $text = "<sup>?</sup>") {
global $connection;
$server_info = $connection->server_info;
function doc_link(array $paths, string $text = "<sup>?</sup>"): string {
$server_info = connection()->server_info;
$version = preg_replace('~^(\d\.?\d).*~s', '\1', $server_info); // two most significant digits
$urls = array(
'sql' => "https://dev.mysql.com/doc/refman/$version/en/",
'sqlite' => "https://www.sqlite.org/",
'pgsql' => "https://www.postgresql.org/docs/" . ($connection->flavor == 'cockroach' ? "current" : $version) . "/",
'pgsql' => "https://www.postgresql.org/docs/" . (connection()->flavor == 'cockroach' ? "current" : $version) . "/",
'mssql' => "https://learn.microsoft.com/en-us/sql/",
'oracle' => "https://www.oracle.com/pls/topic/lookup?ctx=db" . preg_replace('~^.* (\d+)\.(\d+)\.\d+\.\d+\.\d+.*~s', '\1\2', $server_info) . "&id=",
);
if ($connection->flavor == 'maria') {
if (connection()->flavor == 'maria') {
$urls['sql'] = "https://mariadb.com/kb/en/";
$paths['sql'] = (isset($paths['mariadb']) ? $paths['mariadb'] : str_replace(".html", "/", $paths['sql']));
}
@@ -610,12 +558,10 @@ function doc_link($paths, $text = "<sup>?</sup>") {
}
/** Compute size of database
* @param string
* @return string formatted
*/
function db_size($db) {
global $connection;
if (!$connection->select_db($db)) {
function db_size(string $db): string {
if (!connection()->select_db($db)) {
return "?";
}
$return = 0;
@@ -625,15 +571,11 @@ function db_size($db) {
return format_number($return);
}
/** Print SET NAMES if utf8mb4 might be needed
* @param string
* @return null
*/
function set_utf8mb4($create) {
global $connection;
/** Print SET NAMES if utf8mb4 might be needed */
function set_utf8mb4(string $create): void {
static $set = false;
if (!$set && preg_match('~\butf8mb4~i', $create)) { // possible false positive
$set = true;
echo "SET NAMES " . charset($connection) . ";\n\n";
echo "SET NAMES " . charset(connection()) . ";\n\n";
}
}

View File

@@ -3,8 +3,7 @@ namespace Adminer;
error_reporting(24575); // all but E_DEPRECATED (overriding mysqli methods without types is deprecated)
set_error_handler(function ($errno, $errstr) {
// "offset on null" mutes $_GET["fields"][0] if there's no ?fields[]= (62017e3 is a wrong fix for this)
// "Undefined array key" mutes $_GET["q"] if there's no ?q=
// "Undefined offset" and "Undefined index" are older messages for the same thing
return !!preg_match('~^(Trying to access array offset on( value of type)? null|Undefined (array key|offset|index))~', $errstr);
return !!preg_match('~^Undefined (array key|offset|index)~', $errstr);
}, E_WARNING | E_NOTICE); // warning since PHP 8.0

File diff suppressed because it is too large Load Diff

View File

@@ -1,82 +1,55 @@
<?php
namespace Adminer;
/** Return <script> element
* @param string
* @param string
* @return string
*/
function script($source, $trailing = "\n") {
/** Return <script> element */
function script(string $source, string $trailing = "\n"): string {
return "<script" . nonce() . ">$source</script>$trailing";
}
/** Return <script src> element
* @param string
* @return string
*/
function script_src($url) {
/** Return <script src> element */
function script_src(string $url): string {
return "<script src='" . h($url) . "'" . nonce() . "></script>\n";
}
/** Get a nonce="" attribute with CSP nonce
* @return string
*/
function nonce() {
/** Get a nonce="" attribute with CSP nonce */
function nonce(): string {
return ' nonce="' . get_nonce() . '"';
}
/** Get <input type="hidden">
* @param string
* @param string
* @param string|int $value
* @return string HTML
*/
function input_hidden($name, $value = "") {
function input_hidden(string $name, $value = ""): string {
return "<input type='hidden' name='" . h($name) . "' value='" . h($value) . "'>\n";
}
/** Get <input type="hidden" name="token">
* @param string token to use instead of global $token
/** Get CSRF <input type="hidden" name="token">
* @return string HTML
*/
function input_token($special = "") {
global $token;
return input_hidden("token", ($special ?: $token));
function input_token(): string {
return input_hidden("token", get_token());
}
/** Get a target="_blank" attribute
* @return string
*/
function target_blank() {
/** Get a target="_blank" attribute */
function target_blank(): string {
return ' target="_blank" rel="noreferrer noopener"';
}
/** Escape for HTML
* @param string
* @return string
*/
function h($string) {
/** Escape for HTML */
function h(?string $string): string {
return str_replace("\0", "&#0;", htmlspecialchars($string, ENT_QUOTES, 'utf-8'));
}
/** Convert \n to <br>
* @param string
* @return string
*/
function nl_br($string) {
/** Convert \n to <br> */
function nl_br(string $string): string {
return str_replace("\n", "<br>", $string); // nl2br() uses XHTML before PHP 5.3
}
/** Generate HTML checkbox
* @param string
* @param string
* @param bool
* @param string
* @param string
* @param string
* @param string
* @return string
* @param string|int $value
*/
function checkbox($name, $value, $checked, $label = "", $onclick = "", $class = "", $labelled_by = "") {
function checkbox(string $name, $value, ?bool $checked, string $label = "", string $onclick = "", string $class = "", string $labelled_by = ""): string {
$return = "<input type='checkbox' name='$name' value='" . h($value) . "'"
. ($checked ? " checked" : "")
. ($labelled_by ? " aria-labelledby='$labelled_by'" : "")
@@ -87,12 +60,11 @@ function checkbox($name, $value, $checked, $label = "", $onclick = "", $class =
}
/** Generate list of HTML options
* @param array array of strings or arrays (creates optgroup)
* @param mixed
* @param bool always use array keys for value="", otherwise only string keys are used
* @return string
* @param string[]|string[][] $options array of strings or arrays (creates optgroup)
* @param mixed $selected
* @param bool $use_keys always use array keys for value="", otherwise only string keys are used
*/
function optionlist($options, $selected = null, $use_keys = false) {
function optionlist($options, $selected = null, bool $use_keys = false): string {
$return = "";
foreach ($options as $k => $v) {
$opts = array($k => $v);
@@ -115,14 +87,9 @@ function optionlist($options, $selected = null, $use_keys = false) {
}
/** Generate HTML <select>
* @param string
* @param array
* @param string
* @param string
* @param string
* @return string
* @param string[] $options
*/
function html_select($name, $options, $value = "", $onchange = "", $labelled_by = "") {
function html_select(string $name, array $options, ?string $value = "", string $onchange = "", string $labelled_by = ""): string {
return "<select name='" . h($name) . "'"
. ($labelled_by ? " aria-labelledby='$labelled_by'" : "")
. ">" . optionlist($options, $value) . "</select>"
@@ -131,12 +98,9 @@ function html_select($name, $options, $value = "", $onchange = "", $labelled_by
}
/** Generate HTML radio list
* @param string
* @param array
* @param string
* @return string
* @param string[] $options
*/
function html_radios($name, $options, $value = "") {
function html_radios(string $name, array $options, string $value = ""): string {
$return = "";
foreach ($options as $key => $val) {
$return .= "<label><input type='radio' name='" . h($name) . "' value='" . h($key) . "'" . ($key == $value ? " checked" : "") . ">" . h($val) . "</label>";
@@ -144,22 +108,15 @@ function html_radios($name, $options, $value = "") {
return $return;
}
/** Get onclick confirmation
* @param string
* @param string
* @return string
*/
function confirm($message = "", $selector = "qsl('input')") {
/** Get onclick confirmation */
function confirm(string $message = "", string $selector = "qsl('input')"): string {
return script("$selector.onclick = () => confirm('" . ($message ? js_escape($message) : lang('Are you sure?')) . "');", "");
}
/** Print header for hidden fieldset (close by </div></fieldset>)
* @param string
* @param string
* @param bool
* @return null
* @param bool $visible
*/
function print_fieldset($id, $legend, $visible = false) {
function print_fieldset(string $id, string $legend, $visible = false): void {
echo "<fieldset><legend>";
echo "<a href='#fieldset-$id'>$legend</a>";
echo script("qsl('a').onclick = partial(toggle, 'fieldset-$id');", "");
@@ -167,29 +124,18 @@ function print_fieldset($id, $legend, $visible = false) {
echo "<div id='fieldset-$id'" . ($visible ? "" : " class='hidden'") . ">\n";
}
/** Return class='active' if $bold is true
* @param bool
* @param string
* @return string
*/
function bold($bold, $class = "") {
/** Return class='active' if $bold is true */
function bold(bool $bold, string $class = ""): string {
return ($bold ? " class='active $class'" : ($class ? " class='$class'" : ""));
}
/** Escape string for JavaScript apostrophes
* @param string
* @return string
*/
function js_escape($string) {
/** Escape string for JavaScript apostrophes */
function js_escape(string $string): string {
return addcslashes($string, "\r\n'\\/"); // slash for <script>
}
/** Generate page number for pagination
* @param int
* @param int
* @return string
*/
function pagination($page, $current) {
/** Generate page number for pagination */
function pagination(int $page, ?int $current): string {
return " " . ($page == $current
? $page + 1
: '<a href="' . h(remove_from_uri("page") . ($page ? "&page=$page" . ($_GET["next"] ? "&next=" . urlencode($_GET["next"]) : "") : "")) . '">' . ($page + 1) . "</a>"
@@ -197,12 +143,10 @@ function pagination($page, $current) {
}
/** Print hidden fields
* @param array
* @param array
* @param string
* @return bool
* @param mixed[] $process
* @param list<string> $ignore
*/
function hidden_fields($process, $ignore = array(), $prefix = '') {
function hidden_fields(array $process, array $ignore = array(), string $prefix = ''): bool {
$return = false;
foreach ($process as $key => $val) {
if (!in_array($key, $ignore)) {
@@ -217,44 +161,34 @@ function hidden_fields($process, $ignore = array(), $prefix = '') {
return $return;
}
/** Print hidden fields for GET forms
* @return null
*/
function hidden_fields_get() {
/** Print hidden fields for GET forms */
function hidden_fields_get(): void {
echo (sid() ? input_hidden(session_name(), session_id()) : '');
echo (SERVER !== null ? input_hidden(DRIVER, SERVER) : "");
echo input_hidden("username", $_GET["username"]);
}
/** Print enum or set input field
* @param string "radio"|"checkbox"
* @param string
* @param array
* @param mixed string|array
* @param string
* @return null
* @param 'radio'|'checkbox' $type
* @param Field $field
* @param mixed $value string|array
*/
function enum_input($type, $attrs, $field, $value, $empty = null) {
global $adminer;
function enum_input(string $type, string $attrs, array $field, $value, ?string $empty = null): string {
preg_match_all("~'((?:[^']|'')*)'~", $field["length"], $matches);
$return = ($empty !== null ? "<label><input type='$type'$attrs value='$empty'" . ((is_array($value) ? in_array($empty, $value) : $value === $empty) ? " checked" : "") . "><i>" . lang('empty') . "</i></label>" : "");
foreach ($matches[1] as $i => $val) {
$val = stripcslashes(str_replace("''", "'", $val));
$checked = (is_array($value) ? in_array($val, $value) : $value === $val);
$return .= " <label><input type='$type'$attrs value='" . h($val) . "'" . ($checked ? ' checked' : '') . '>' . h($adminer->editVal($val, $field)) . '</label>';
$return .= " <label><input type='$type'$attrs value='" . h($val) . "'" . ($checked ? ' checked' : '') . '>' . h(adminer()->editVal($val, $field)) . '</label>';
}
return $return;
}
/** Print edit input field
* @param array one field from fields()
* @param mixed
* @param string
* @param bool
* @return null
* @param Field|RoutineField $field
* @param mixed $value
*/
function input($field, $value, $function, $autofocus = false) {
global $driver, $adminer;
function input(array $field, $value, ?string $function, ?bool $autofocus = false): void {
$name = h(bracket_escape($field["field"]));
echo "<td class='function'>";
if (is_array($value) && !$function) {
@@ -265,17 +199,18 @@ function input($field, $value, $function, $autofocus = false) {
if ($reset && !$_POST["save"]) {
$function = null;
}
$functions = (isset($_GET["select"]) || $reset ? array("orig" => lang('original')) : array()) + $adminer->editFunctions($field);
$functions = (isset($_GET["select"]) || $reset ? array("orig" => lang('original')) : array()) + adminer()->editFunctions($field);
$disabled = stripos($field["default"], "GENERATED ALWAYS AS ") === 0 ? " disabled=''" : "";
$attrs = " name='fields[$name]'$disabled" . ($autofocus ? " autofocus" : "");
$enums = $driver->enumLength($field);
$enums = driver()->enumLength($field);
if ($enums) {
$field["type"] = "enum";
$field["length"] = $enums;
}
echo $driver->unconvertFunction($field) . " ";
echo driver()->unconvertFunction($field) . " ";
$table = $_GET["edit"] ?: $_GET["select"];
if ($field["type"] == "enum") {
echo h($functions[""]) . "<td>" . $adminer->editInput($_GET["edit"], $field, $attrs, $value);
echo h($functions[""]) . "<td>" . adminer()->editInput($table, $field, $attrs, $value);
} else {
$has_function = (in_array($function, $functions) || isset($functions[$function]));
echo (count($functions) > 1
@@ -284,7 +219,7 @@ function input($field, $value, $function, $autofocus = false) {
. script("qsl('select').onchange = functionChange;", "")
: h(reset($functions))
) . '<td>';
$input = $adminer->editInput($_GET["edit"], $field, $attrs, $value); // usage in call is without a table
$input = adminer()->editInput($table, $field, $attrs, $value); // usage in call is without a table
if ($input != "") {
echo $input;
} elseif (preg_match('~bool~', $field["type"])) {
@@ -295,7 +230,7 @@ function input($field, $value, $function, $autofocus = false) {
foreach ($matches[1] as $i => $val) {
$val = stripcslashes(str_replace("''", "'", $val));
$checked = in_array($val, explode(",", $value), true);
echo " <label><input type='checkbox' name='fields[$name][$i]' value='" . h($val) . "'" . ($checked ? ' checked' : '') . ">" . h($adminer->editVal($val, $field)) . '</label>';
echo " <label><input type='checkbox' name='fields[$name][$i]' value='" . h($val) . "'" . ($checked ? ' checked' : '') . ">" . h(adminer()->editVal($val, $field)) . '</label>';
}
} elseif (preg_match('~blob|bytea|raw|file~', $field["type"]) && ini_bool("file_uploads")) {
echo "<input type='file' name='fields-$name'>";
@@ -311,7 +246,7 @@ function input($field, $value, $function, $autofocus = false) {
echo "<textarea$attrs>" . h($value) . '</textarea>';
} else {
// int(3) is only a display hint
$types = $driver->types();
$types = driver()->types();
$maxlength = (!preg_match('~int~', $field["type"]) && preg_match('~^(\d+)(,(\d+))?$~', $field["length"], $match)
? ((preg_match("~binary~", $field["type"]) ? 2 : 1) * $match[1] + ($match[3] ? 1 : 0) + ($match[2] && !$field["unsigned"] ? 1 : 0))
: ($types[$field["type"]] ? $types[$field["type"]] + ($field["unsigned"] ? 0 : 1) : 0)
@@ -327,7 +262,7 @@ function input($field, $value, $function, $autofocus = false) {
. "$attrs>"
;
}
echo $adminer->editHint($_GET["edit"], $field, $value);
echo adminer()->editHint($table, $field, $value);
// skip 'original'
$first = 0;
foreach ($functions as $key => $val) {
@@ -343,18 +278,17 @@ function input($field, $value, $function, $autofocus = false) {
}
/** Process edit input field
* @param one field from fields()
* @return string or false to leave the original value
* @param Field|RoutineField $field
* @return mixed false to leave the original value
*/
function process_input($field) {
global $adminer, $driver;
function process_input(array $field) {
if (stripos($field["default"], "GENERATED ALWAYS AS ") === 0) {
return null;
return;
}
$idf = bracket_escape($field["field"]);
$function = $_POST["function"][$idf];
$function = idx($_POST["function"], $idf);
$value = $_POST["fields"][$idf];
if ($field["type"] == "enum" || $driver->enumLength($field)) {
if ($field["type"] == "enum" || driver()->enumLength($field)) {
if ($value == -1) {
return false;
}
@@ -387,24 +321,22 @@ function process_input($field) {
if (!is_string($file)) {
return false; //! report errors
}
return $driver->quoteBinary($file);
return driver()->quoteBinary($file);
}
return $adminer->processInput($field, $value, $function);
return adminer()->processInput($field, $value, $function);
}
/** Print results of search in all tables
* @uses $_GET["where"][0]
* @uses $_POST["tables"]
* @return null
*/
function search_tables() {
global $adminer, $connection;
function search_tables(): void {
$_GET["where"][0]["val"] = $_POST["query"];
$sep = "<ul>\n";
foreach (table_status('', true) as $table => $table_status) {
$name = $adminer->tableName($table_status);
$name = adminer()->tableName($table_status);
if (isset($table_status["Engine"]) && $name != "" && (!$_POST["tables"] || in_array($table, $_POST["tables"]))) {
$result = $connection->query("SELECT" . limit("1 FROM " . table($table), " WHERE " . implode(" AND ", $adminer->selectSearchProcess(fields($table), array())), 1));
$result = connection()->query("SELECT" . limit("1 FROM " . table($table), " WHERE " . implode(" AND ", adminer()->selectSearchProcess(fields($table), array())), 1));
if (!$result || $result->fetch_row()) {
$print = "<a href='" . h(ME . "select=" . urlencode($table) . "&where[0][op]=" . urlencode($_GET["where"][0]["op"]) . "&where[0][val]=" . urlencode($_GET["where"][0]["val"])) . "'>$name</a>";
echo "$sep<li>" . ($result ? $print : "<p class='error'>$print: " . error()) . "\n";
@@ -416,31 +348,26 @@ function search_tables() {
}
/** Return events to display help on mouse over
* @param string JS expression
* @param bool JS expression
* @return string
* @param string $command JS expression
* @param int $side 0 top, 1 left
*/
function on_help($command, $side = 0) {
function on_help(string $command, int $side = 0): string {
return script("mixin(qsl('select, input'), {onmouseover: function (event) { helpMouseover.call(this, event, $command, $side) }, onmouseout: helpMouseout});", "");
}
/** Print edit data form
* @param string
* @param array
* @param mixed
* @param bool
* @return null
* @param Field[] $fields
* @param mixed $row
*/
function edit_form($table, $fields, $row, $update) {
global $adminer, $error;
$table_name = $adminer->tableName(table_status1($table, true));
function edit_form(string $table, array $fields, $row, ?bool $update, string $error = ''): void {
$table_name = adminer()->tableName(table_status1($table, true));
page_header(
($update ? lang('Edit') : lang('Insert')),
$error,
array("select" => array($table, $table_name)),
$table_name
);
$adminer->editRowPrint($table, $fields, $row, $update);
adminer()->editRowPrint($table, $fields, $row, $update);
if ($row === false) {
echo "<p class='error'>" . lang('No rows.') . "\n";
return;
@@ -452,8 +379,8 @@ function edit_form($table, $fields, $row, $update) {
echo "<table class='layout'>" . script("qsl('table').onkeydown = editingKeydown;");
$autofocus = !$_POST;
foreach ($fields as $name => $field) {
echo "<tr><th>" . $adminer->fieldName($field);
$default = $_GET["set"][bracket_escape($name)];
echo "<tr><th>" . adminer()->fieldName($field);
$default = idx($_GET["set"], bracket_escape($name));
if ($default === null) {
$default = $field["default"];
if ($field["type"] == "bit" && preg_match("~^b'([01]*)'\$~", $default, $regs)) {
@@ -474,10 +401,10 @@ function edit_form($table, $fields, $row, $update) {
)
);
if (!$_POST["save"] && is_string($value)) {
$value = $adminer->editVal($value, $field);
$value = adminer()->editVal($value, $field);
}
$function = ($_POST["save"]
? (string) $_POST["function"][$name]
? idx($_POST["function"], $name, "")
: ($update && preg_match('~^CURRENT_TIMESTAMP~i', $field["on_update"])
? "now"
: ($value === false ? null : ($value !== null ? '' : 'NULL'))
@@ -507,7 +434,7 @@ function edit_form($table, $fields, $row, $update) {
echo "<tr>"
. "<th><input name='field_keys[]'>"
. script("qsl('input').oninput = fieldChange;")
. "<td class='function'>" . html_select("field_funs[]", $adminer->editFunctions(array("null" => isset($_GET["select"]))))
. "<td class='function'>" . html_select("field_funs[]", adminer()->editFunctions(array("null" => isset($_GET["select"]))))
. "<td><input name='field_vals[]'>"
. "\n"
;
@@ -534,3 +461,18 @@ function edit_form($table, $fields, $row, $update) {
echo input_token();
echo "</form>\n";
}
/** Shorten UTF-8 string
* @return string escaped string with appended ...
*/
function shorten_utf8(string $string, int $length = 80, string $suffix = ""): string {
if (!preg_match("(^(" . repeat_pattern("[\t\r\n -\x{10FFFF}]", $length) . ")($)?)u", $string, $match)) { // ~s causes trash in $match[2] under some PHP versions, (.|\n) is slow
preg_match("(^(" . repeat_pattern("[\t\r\n -~]", $length) . ")($)?)", $string, $match);
}
return h($match[1]) . $suffix . (isset($match[2]) ? "" : "<i>…</i>");
}
/** Get button with icon */
function icon(string $icon, string $name, string $html, string $title): string {
return "<button type='submit' name='$name' title='" . h($title) . "' class='icon icon-$icon'><span>$html</span></button>";
}

View File

@@ -3,81 +3,76 @@ namespace Adminer;
// not used in a single language version
$langs = array(
'en' => 'English', // Jakub Vrána - https://www.vrana.cz
'ar' => 'العربية', // Y.M Amine - Algeria - nbr7@live.fr
'bg' => 'Български', // Deyan Delchev
'bn' => 'বাংলা', // Dipak Kumar - dipak.ndc@gmail.com, Hossain Ahmed Saiman - hossain.ahmed@altscope.com
'bs' => 'Bosanski', // Emir Kurtovic
'ca' => 'Català', // Joan Llosas
'cs' => 'Čeština', // Jakub Vrána - https://www.vrana.cz
'da' => 'Dansk', // Jarne W. Beutnagel - jarne@beutnagel.dk
'de' => 'Deutsch', // Klemens Häckel - http://clickdimension.wordpress.com
'el' => 'Ελληνικά', // Dimitrios T. Tanis - jtanis@tanisfood.gr
'es' => 'Español', // Klemens Häckel - http://clickdimension.wordpress.com
'et' => 'Eesti', // Priit Kallas
'fa' => 'فارسی', // mojtaba barghbani - Iran - mbarghbani@gmail.com, Nima Amini - http://nimlog.com
'fi' => 'Suomi', // Finnish - Kari Eveli - http://www.lexitec.fi/
'fr' => 'Français', // Francis Gagné, Aurélien Royer
'gl' => 'Galego', // Eduardo Penabad Ramos
'he' => 'עברית', // Binyamin Yawitz - https://stuff-group.com/
'hu' => 'Magyar', // Borsos Szilárd (Borsosfi) - http://www.borsosfi.hu, info@borsosfi.hu
'id' => 'Bahasa Indonesia', // Ivan Lanin - http://ivan.lanin.org
'it' => 'Italiano', // Alessandro Fiorotto, Paolo Asperti
'ja' => '日本語', // Hitoshi Ozawa - http://sourceforge.jp/projects/oss-ja-jpn/releases/
'ka' => 'ქართული', // Saba Khmaladze skhmaladze@uglt.org
'ko' => '한국어', // dalli - skcha67@gmail.com
'lt' => 'Lietuvių', // Paulius Leščinskas - http://www.lescinskas.lt
'lv' => 'Latviešu', // Kristaps Lediņš - https://krysits.com
'ms' => 'Bahasa Melayu', // Pisyek
'nl' => 'Nederlands', // Maarten Balliauw - http://blog.maartenballiauw.be
'no' => 'Norsk', // Iver Odin Kvello, mupublishing.com
'pl' => 'Polski', // Radosław Kowalewski - http://srsbiz.pl/
'pt' => 'Português', // André Dias
'pt-br' => 'Português (Brazil)', // Gian Live - gian@live.com, Davi Alexandre davi@davialexandre.com.br, RobertoPC - http://www.robertopc.com.br
'ro' => 'Limba Română', // .nick .messing - dot.nick.dot.messing@gmail.com
'ru' => 'Русский', // Maksim Izmaylov; Andre Polykanine - https://github.com/Oire/
'sk' => 'Slovenčina', // Ivan Suchy - http://www.ivansuchy.com, Juraj Krivda - http://www.jstudio.cz
'sl' => 'Slovenski', // Matej Ferlan - www.itdinamik.com, matej.ferlan@itdinamik.com
'sr' => 'Српски', // Nikola Radovanović - cobisimo@gmail.com
'sv' => 'Svenska', // rasmusolle - https://github.com/rasmusolle
'ta' => 'த‌மிழ்', // G. Sampath Kumar, Chennai, India, sampathkumar11@gmail.com
'th' => 'ภาษาไทย', // Panya Saraphi, elect.tu@gmail.com - http://www.opencart2u.com/
'tr' => 'Türkçe', // Bilgehan Korkmaz - turktron.com
'uk' => 'Українська', // Valerii Kryzhov
'uz' => 'Oʻzbekcha', // Junaydullaev Inoyatullokhon - https://av.uz/
'vi' => 'Tiếng Việt', // Giang Manh @ manhgd google mail
'zh' => '简体中文', // Mr. Lodar, vea - urn2.net - vea.urn2@gmail.com
'zh-tw' => '繁體中文', // http://tzangms.com
);
/** Get current language
* @return string
/** Get available languages
* @return string[]
*/
function get_lang() {
global $LANG;
return $LANG;
function langs(): array {
return array(
'en' => 'English', // Jakub Vrána - https://www.vrana.cz
'ar' => 'العربية', // Y.M Amine - Algeria - nbr7@live.fr
'bg' => 'Български', // Deyan Delchev
'bn' => 'বাংলা', // Dipak Kumar - dipak.ndc@gmail.com, Hossain Ahmed Saiman - hossain.ahmed@altscope.com
'bs' => 'Bosanski', // Emir Kurtovic
'ca' => 'Català', // Joan Llosas
'cs' => 'Čeština', // Jakub Vrána - https://www.vrana.cz
'da' => 'Dansk', // Jarne W. Beutnagel - jarne@beutnagel.dk
'de' => 'Deutsch', // Klemens Häckel - http://clickdimension.wordpress.com
'el' => 'Ελληνικά', // Dimitrios T. Tanis - jtanis@tanisfood.gr
'es' => 'Español', // Klemens Häckel - http://clickdimension.wordpress.com
'et' => 'Eesti', // Priit Kallas
'fa' => 'فارسی', // mojtaba barghbani - Iran - mbarghbani@gmail.com, Nima Amini - http://nimlog.com
'fi' => 'Suomi', // Finnish - Kari Eveli - http://www.lexitec.fi/
'fr' => 'Français', // Francis Gagné, Aurélien Royer
'gl' => 'Galego', // Eduardo Penabad Ramos
'he' => 'עברית', // Binyamin Yawitz - https://stuff-group.com/
'hu' => 'Magyar', // Borsos Szilárd (Borsosfi) - http://www.borsosfi.hu, info@borsosfi.hu
'id' => 'Bahasa Indonesia', // Ivan Lanin - http://ivan.lanin.org
'it' => 'Italiano', // Alessandro Fiorotto, Paolo Asperti
'ja' => '日本語', // Hitoshi Ozawa - http://sourceforge.jp/projects/oss-ja-jpn/releases/
'ka' => 'ქართული', // Saba Khmaladze skhmaladze@uglt.org
'ko' => '한국어', // dalli - skcha67@gmail.com
'lt' => 'Lietuvių', // Paulius Leščinskas - http://www.lescinskas.lt
'lv' => 'Latviešu', // Kristaps Lediņš - https://krysits.com
'ms' => 'Bahasa Melayu', // Pisyek
'nl' => 'Nederlands', // Maarten Balliauw - http://blog.maartenballiauw.be
'no' => 'Norsk', // Iver Odin Kvello, mupublishing.com
'pl' => 'Polski', // Radosław Kowalewski - http://srsbiz.pl/
'pt' => 'Português', // André Dias
'pt-br' => 'Português (Brazil)', // Gian Live - gian@live.com, Davi Alexandre davi@davialexandre.com.br, RobertoPC - http://www.robertopc.com.br
'ro' => 'Limba Română', // .nick .messing - dot.nick.dot.messing@gmail.com
'ru' => 'Русский', // Maksim Izmaylov; Andre Polykanine - https://github.com/Oire/
'sk' => 'Slovenčina', // Ivan Suchy - http://www.ivansuchy.com, Juraj Krivda - http://www.jstudio.cz
'sl' => 'Slovenski', // Matej Ferlan - www.itdinamik.com, matej.ferlan@itdinamik.com
'sr' => 'Српски', // Nikola Radovanović - cobisimo@gmail.com
'sv' => 'Svenska', // rasmusolle - https://github.com/rasmusolle
'ta' => 'த‌மிழ்', // G. Sampath Kumar, Chennai, India, sampathkumar11@gmail.com
'th' => 'ภาษาไทย', // Panya Saraphi, elect.tu@gmail.com - http://www.opencart2u.com/
'tr' => 'Türkçe', // Bilgehan Korkmaz - turktron.com
'uk' => 'Українська', // Valerii Kryzhov
'uz' => 'Oʻzbekcha', // Junaydullaev Inoyatullokhon - https://av.uz/
'vi' => 'Tiếng Việt', // Giang Manh @ manhgd google mail
'zh' => '简体中文', // Mr. Lodar, vea - urn2.net - vea.urn2@gmail.com
'zh-tw' => '繁體中文', // http://tzangms.com
);
}
/** Translate string
* @param string
* @param int
* @return string
* @param literal-string $idf
* @param float|string $number
*/
// this is matched by compile.php
function lang($idf, $number = null) {
global $LANG, $translations;
$translation = ($translations[$idf] ?: $idf);
function lang(string $idf, $number = null): string {
// this is matched by compile.php
$translation = (Lang::$translations[$idf] ?: $idf);
if (is_array($translation)) {
// this is matched by compile.php
$pos = ($number == 1 ? 0
: ($LANG == 'cs' || $LANG == 'sk' ? ($number && $number < 5 ? 1 : 2) // different forms for 1, 2-4, other
: ($LANG == 'fr' ? (!$number ? 0 : 1) // different forms for 0-1, other
: ($LANG == 'pl' ? ($number % 10 > 1 && $number % 10 < 5 && $number / 10 % 10 != 1 ? 1 : 2) // different forms for 1, 2-4 except 12-14, other
: ($LANG == 'sl' ? ($number % 100 == 1 ? 0 : ($number % 100 == 2 ? 1 : ($number % 100 == 3 || $number % 100 == 4 ? 2 : 3))) // different forms for 1, 2, 3-4, other
: ($LANG == 'lt' ? ($number % 10 == 1 && $number % 100 != 11 ? 0 : ($number % 10 > 1 && $number / 10 % 10 != 1 ? 1 : 2)) // different forms for 1, 12-19, other
: ($LANG == 'lv' ? ($number % 10 == 1 && $number % 100 != 11 ? 0 : ($number ? 1 : 2)) // different forms for 1 except 11, other, 0
: (in_array($LANG, array('bs', 'ru', 'sr', 'uk')) ? ($number % 10 == 1 && $number % 100 != 11 ? 0 : ($number % 10 > 1 && $number % 10 < 5 && $number / 10 % 10 != 1 ? 1 : 2)) // different forms for 1 except 11, 2-4 except 12-14, other
: (LANG == 'cs' || LANG == 'sk' ? ($number && $number < 5 ? 1 : 2) // different forms for 1, 2-4, other
: (LANG == 'fr' ? (!$number ? 0 : 1) // different forms for 0-1, other
: (LANG == 'pl' ? ($number % 10 > 1 && $number % 10 < 5 && $number / 10 % 10 != 1 ? 1 : 2) // different forms for 1, 2-4 except 12-14, other
: (LANG == 'sl' ? ($number % 100 == 1 ? 0 : ($number % 100 == 2 ? 1 : ($number % 100 == 3 || $number % 100 == 4 ? 2 : 3))) // different forms for 1, 2, 3-4, other
: (LANG == 'lt' ? ($number % 10 == 1 && $number % 100 != 11 ? 0 : ($number % 10 > 1 && $number / 10 % 10 != 1 ? 1 : 2)) // different forms for 1, 12-19, other
: (LANG == 'lv' ? ($number % 10 == 1 && $number % 100 != 11 ? 0 : ($number ? 1 : 2)) // different forms for 1 except 11, other, 0
: (in_array(LANG, array('bs', 'ru', 'sr', 'uk')) ? ($number % 10 == 1 && $number % 100 != 11 ? 0 : ($number % 10 > 1 && $number % 10 < 5 && $number / 10 % 10 != 1 ? 1 : 2)) // different forms for 1 except 11, 2-4 except 12-14, other
: 1)))))))) // different forms for 1, other
; // http://www.gnu.org/software/gettext/manual/html_node/Plural-forms.html
$translation = $translation[$pos];
@@ -92,27 +87,25 @@ function lang($idf, $number = null) {
return vsprintf($format, $args);
}
function switch_lang() {
global $LANG, $langs;
function switch_lang(): void {
echo "<form action='' method='post'>\n<div id='lang'>";
echo lang('Language') . ": " . html_select("lang", $langs, $LANG, "this.form.submit();");
echo lang('Language') . ": " . html_select("lang", langs(), LANG, "this.form.submit();");
echo " <input type='submit' value='" . lang('Use') . "' class='hidden'>\n";
echo input_token(get_token()); // $token may be empty in auth.inc.php
echo input_token();
echo "</div>\n</form>\n";
}
if (isset($_POST["lang"]) && verify_token()) { // $error not yet available
cookie("adminer_lang", $_POST["lang"]);
$_SESSION["lang"] = $_POST["lang"]; // cookies may be disabled
$_SESSION["translations"] = array(); // used in compiled version
redirect(remove_from_uri());
}
$LANG = "en";
if (isset($langs[$_COOKIE["adminer_lang"]])) {
if (idx(langs(), $_COOKIE["adminer_lang"])) {
cookie("adminer_lang", $_COOKIE["adminer_lang"]);
$LANG = $_COOKIE["adminer_lang"];
} elseif (isset($langs[$_SESSION["lang"]])) {
} elseif (idx(langs(), $_SESSION["lang"])) {
$LANG = $_SESSION["lang"];
} else {
$accept_language = array();
@@ -122,14 +115,20 @@ if (isset($langs[$_COOKIE["adminer_lang"]])) {
}
arsort($accept_language);
foreach ($accept_language as $key => $q) {
if (isset($langs[$key])) {
if (idx(langs(), $key)) {
$LANG = $key;
break;
}
$key = preg_replace('~-.*~', '', $key);
if (!isset($accept_language[$key]) && isset($langs[$key])) {
if (!isset($accept_language[$key]) && idx(langs(), $key)) {
$LANG = $key;
break;
}
}
}
define('Adminer\LANG', $LANG);
class Lang {
/** @var array<literal-string, string|list<string>> */ static array $translations;
}

View File

@@ -3,29 +3,31 @@ namespace Adminer;
// PDO can be used in several database drivers
if (extension_loaded('pdo')) {
abstract class PdoDb {
public $flavor = '', $server_info, $affected_rows, $errno, $error;
protected $pdo;
private $result;
abstract class PdoDb extends SqlDb {
protected \PDO $pdo;
function dsn($dsn, $username, $password, $options = array()) {
/** Connect to server using DSN
* @param mixed[] $options
* @return string error message
*/
function dsn(string $dsn, string $username, string $password, array $options = array()): string {
$options[\PDO::ATTR_ERRMODE] = \PDO::ERRMODE_SILENT;
$options[\PDO::ATTR_STATEMENT_CLASS] = array('Adminer\PdoDbStatement');
$options[\PDO::ATTR_STATEMENT_CLASS] = array('Adminer\PdoResult');
try {
$this->pdo = new \PDO($dsn, $username, $password, $options);
} catch (\Exception $ex) {
auth_error(h($ex->getMessage()));
return $ex->getMessage();
}
$this->server_info = @$this->pdo->getAttribute(\PDO::ATTR_SERVER_VERSION);
return '';
}
abstract function select_db($database);
function quote($string) {
function quote(string $string): string {
return $this->pdo->quote($string);
}
function query($query, $unbuffered = false) {
function query(string $query, bool $unbuffered = false) {
/** @var Result|bool */
$result = $this->pdo->query($query);
$this->error = "";
if (!$result) {
@@ -39,13 +41,9 @@ if (extension_loaded('pdo')) {
return $result;
}
function multi_query($query) {
return $this->result = $this->query($query);
}
function store_result($result = null) {
if (!$result) {
$result = $this->result;
$result = $this->multi;
if (!$result) {
return false;
}
@@ -58,25 +56,18 @@ if (extension_loaded('pdo')) {
return true;
}
function next_result() {
if (!$this->result) {
function next_result(): bool {
/** @var PdoResult|bool */
$result = $this->multi;
if (!is_object($result)) {
return false;
}
$this->result->_offset = 0;
return @$this->result->nextRowset(); // @ - PDO_PgSQL doesn't support it
}
function result($query, $field = 0) {
$result = $this->query($query);
if (!$result) {
return false;
}
$row = $result->fetch();
return $row ? $row[$field] : false;
$result->_offset = 0;
return @$result->nextRowset(); // @ - PDO_PgSQL doesn't support it
}
}
class PdoDbStatement extends \PDOStatement {
class PdoResult extends \PDOStatement {
public $_offset = 0, $num_rows;
function fetch_assoc() {
@@ -87,11 +78,7 @@ if (extension_loaded('pdo')) {
return $this->fetch(\PDO::FETCH_NUM);
}
function fetch_column($field) {
return $this->fetchColumn($field);
}
function fetch_field() {
function fetch_field(): \stdClass {
$row = (object) $this->getColumnMeta($this->_offset++);
$type = $row->pdo_type;
$row->type = ($type == \PDO::PARAM_INT ? 0 : 15);

View File

@@ -1,13 +1,17 @@
<?php
namespace Adminer;
class Plugins extends Adminer {
public $plugins; ///< @var protected(set) array
class Plugins {
/** @var true[] */ private static array $append = array('dumpFormat' => true, 'dumpOutput' => true, 'editRowPrint' => true, 'editFunctions' => true); // these hooks expect the value to be appended to the result
/** @var list<object> @visibility protected(set) */ public array $plugins;
/** @visibility protected(set) */ public string $error = ''; // HTML
/** @var list<object>[] */ private array $hooks = array();
/** Register plugins
* @param array object instances or null to autoload plugins from adminer-plugins/
* @param ?list<object> $plugins object instances or null to autoload plugins from adminer-plugins/
*/
function __construct($plugins) {
function __construct(?array $plugins) {
if ($plugins === null) {
$plugins = array();
$basename = "adminer-plugins";
@@ -29,6 +33,7 @@ class Plugins extends Adminer {
}
foreach (get_declared_classes() as $class) {
if (!$plugins[$class] && preg_match('~^Adminer\w~i', $class)) {
// we need to use reflection because PHP 7.1 throws ArgumentCountError for missing arguments but older versions issue a warning
$reflection = new \ReflectionClass($class);
$constructor = $reflection->getConstructor();
if ($constructor && $constructor->getNumberOfRequiredParameters()) {
@@ -40,388 +45,41 @@ class Plugins extends Adminer {
}
}
$this->plugins = $plugins;
$adminer = new Adminer;
$plugins[] = $adminer;
$reflection = new \ReflectionObject($adminer);
foreach ($reflection->getMethods() as $method) {
foreach ($plugins as $plugin) {
$name = $method->getName();
if (method_exists($plugin, $name)) {
$this->hooks[$name][] = $plugin;
}
}
}
}
private function callParent($function, $args) {
return call_user_func_array(array('parent', $function), $args);
}
private function applyPlugin($function, $params) {
/**
* @param literal-string $name
* @param mixed[] $params
* @return mixed
*/
function __call(string $name, array $params) {
$args = array();
foreach ($params as $key => $val) {
// some plugins accept params by reference - we don't need to propage it outside, just to the other plugins
$args[] = &$params[$key];
}
foreach ($this->plugins as $plugin) {
if (method_exists($plugin, $function)) {
$return = call_user_func_array(array($plugin, $function), $args);
if ($return !== null) {
return $return;
}
}
}
return $this->callParent($function, $args);
}
private function appendPlugin($function, $args) {
$return = $this->callParent($function, $args);
foreach ($this->plugins as $plugin) {
if (method_exists($plugin, $function)) {
$value = call_user_func_array(array($plugin, $function), $args);
if ($value) {
$return += $value;
$return = null;
foreach ($this->hooks[$name] as $plugin) {
$value = call_user_func_array(array($plugin, $name), $args);
if ($value !== null) {
if (!self::$append[$name]) { // non-null value from non-appending method short-circuits the other plugins
return $value;
}
$return = $value + (array) $return;
}
}
return $return;
}
// appendPlugin
function dumpFormat() {
$args = func_get_args();
return $this->appendPlugin(__FUNCTION__, $args);
}
function dumpOutput() {
$args = func_get_args();
return $this->appendPlugin(__FUNCTION__, $args);
}
function editRowPrint($table, $fields, $row, $update) {
$args = func_get_args();
return $this->appendPlugin(__FUNCTION__, $args);
}
function editFunctions($field) {
$args = func_get_args();
return $this->appendPlugin(__FUNCTION__, $args);
}
// applyPlugin
function name() {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function credentials() {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function connectSsl() {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function permanentLogin($create = false) {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function bruteForceKey() {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function serverName($server) {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function database() {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function schemas() {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function databases($flush = true) {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function queryTimeout() {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function headers() {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function csp() {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function head($dark = null) {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function css() {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function loginForm() {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function loginFormField($name, $heading, $value) {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function login($login, $password) {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function tableName($tableStatus) {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function fieldName($field, $order = 0) {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function selectLinks($tableStatus, $set = "") {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function foreignKeys($table) {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function backwardKeys($table, $tableName) {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function backwardKeysPrint($backwardKeys, $row) {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function selectQuery($query, $start, $failed = false) {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function sqlCommandQuery($query) {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function sqlPrintAfter() {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function rowDescription($table) {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function rowDescriptions($rows, $foreignKeys) {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function selectLink($val, $field) {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function selectVal($val, $link, $field, $original) {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function editVal($val, $field) {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function tableStructurePrint($fields, $tableStatus = null) {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function tableIndexesPrint($indexes) {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function selectColumnsPrint($select, $columns) {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function selectSearchPrint($where, $columns, $indexes) {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function selectOrderPrint($order, $columns, $indexes) {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function selectLimitPrint($limit) {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function selectLengthPrint($text_length) {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function selectActionPrint($indexes) {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function selectCommandPrint() {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function selectImportPrint() {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function selectEmailPrint($emailFields, $columns) {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function selectColumnsProcess($columns, $indexes) {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function selectSearchProcess($fields, $indexes) {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function selectOrderProcess($fields, $indexes) {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function selectLimitProcess() {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function selectLengthProcess() {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function selectEmailProcess($where, $foreignKeys) {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function selectQueryBuild($select, $where, $group, $order, $limit, $page) {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function messageQuery($query, $time, $failed = false) {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function editInput($table, $field, $attrs, $value) {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function editHint($table, $field, $value) {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function processInput($field, $value, $function = "") {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function dumpDatabase($db) {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function dumpTable($table, $style, $is_view = 0) {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function dumpData($table, $style, $query) {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function dumpFilename($identifier) {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function dumpHeaders($identifier, $multi_table = false) {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function dumpFooter() {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function importServerPath() {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function homepage() {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function navigation($missing) {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function syntaxHighlighting($tables) {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function databasesPrint($missing) {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
function tablesPrint($tables) {
$args = func_get_args();
return $this->applyPlugin(__FUNCTION__, $args);
}
}

View File

@@ -2,18 +2,19 @@
namespace Adminer;
class TmpFile {
private $handler, $size;
/** @var resource */ private $handler;
/** @visibility protected(set) */ public int $size;
function __construct() {
$this->handler = tmpfile();
}
function write($contents) {
function write(string $contents): void {
$this->size += strlen($contents);
fwrite($this->handler, $contents);
}
function send() {
function send(): void {
fseek($this->handler, 0);
fpassthru($this->handler);
fclose($this->handler);

View File

@@ -1,4 +1,4 @@
<?php
namespace Adminer;
$VERSION = "5.1.0";
const VERSION = "5.1.1";

View File

@@ -6,7 +6,7 @@ namespace Adminer;
* @link http://www.coolcode.cn/?action=show&id=128
*/
function int32($n) {
function int32(int $n): int {
while ($n >= 2147483648) {
$n -= 4294967296;
}
@@ -16,7 +16,10 @@ function int32($n) {
return (int) $n;
}
function long2str($v, $w) {
/**
* @param int[] $v
*/
function long2str(array $v, bool $w): string {
$s = '';
foreach ($v as $val) {
$s .= pack('V', $val);
@@ -27,7 +30,10 @@ function long2str($v, $w) {
return $s;
}
function str2long($s, $w) {
/**
* @return int[]
*/
function str2long(string $s, bool $w): array {
$v = array_values(unpack('V*', str_pad($s, 4 * ceil(strlen($s) / 4), "\0")));
if ($w) {
$v[] = strlen($s);
@@ -35,16 +41,15 @@ function str2long($s, $w) {
return $v;
}
function xxtea_mx($z, $y, $sum, $k) {
function xxtea_mx(int $z, int $y, int $sum, int $k): int {
return int32((($z >> 5 & 0x7FFFFFF) ^ $y << 2) + (($y >> 3 & 0x1FFFFFFF) ^ $z << 4)) ^ int32(($sum ^ $y) + ($k ^ $z));
}
/** Cipher
* @param string plain-text password
* @param string
* @param string $str plain-text password
* @return string binary cipher
*/
function encrypt_string($str, $key) {
function encrypt_string(string $str, string $key): string {
if ($str == "") {
return "";
}
@@ -73,11 +78,10 @@ function encrypt_string($str, $key) {
}
/** Decipher
* @param string binary cipher
* @param string
* @return string plain-text password
* @param string $str binary cipher
* @return string|false plain-text password
*/
function decrypt_string($str, $key) {
function decrypt_string(string $str, string $key) {
if ($str == "") {
return "";
}

View File

@@ -3,7 +3,7 @@ namespace Adminer;
$TABLE = $_GET["indexes"];
$index_types = array("PRIMARY", "UNIQUE", "INDEX");
$table_status = table_status($TABLE, true);
$table_status = table_status1($TABLE, true);
if (preg_match('~MyISAM|M?aria' . (min_version(5.6, '10.0.5') ? '|InnoDB' : '') . '~i', $table_status["Engine"])) {
$index_types[] = "FULLTEXT";
}
@@ -33,8 +33,8 @@ if ($_POST && !$error && !$_POST["add"] && !$_POST["drop_col"]) {
ksort($index["columns"]);
foreach ($index["columns"] as $key => $column) {
if ($column != "") {
$length = $index["lengths"][$key];
$desc = $index["descs"][$key];
$length = idx($index["lengths"], $key);
$desc = idx($index["descs"], $key);
$set[] = idf_escape($column) . ($length ? "(" . (+$length) . ")" : "") . ($desc ? " DESC" : "");
$columns[] = $column;
$lengths[] = ($length ?: null);
@@ -112,7 +112,7 @@ if ($lengths || support("descidx")) {
}
?>
<th id="label-name"><?php echo lang('Name'); ?>
<th><noscript><?php echo "<input type='image' class='icon' name='add[0]' src='../adminer/static/plus.gif' alt='+' title='" . lang('Add next') . "'>"; ?></noscript>
<th><noscript><?php echo icon("plus", "add[0]", "+", lang('Add next')); ?></noscript>
</thead>
<?php
if ($primary) {
@@ -139,14 +139,14 @@ foreach ($row["indexes"] as $index) {
"partial(" . ($i == count($index["columns"]) ? "indexesAddColumn" : "indexesChangeColumn") . ", '" . js_escape(JUSH == "sql" ? "" : $_GET["indexes"] . "_") . "')"
);
echo "<span class='idxopts" . ($show_options ? "" : " hidden") . "'>";
echo ($lengths ? "<input type='number' name='indexes[$j][lengths][$i]' class='size' value='" . h($index["lengths"][$key]) . "' title='" . lang('Length') . "'>" : "");
echo (support("descidx") ? checkbox("indexes[$j][descs][$i]", 1, $index["descs"][$key], lang('descending')) : "");
echo ($lengths ? "<input type='number' name='indexes[$j][lengths][$i]' class='size' value='" . h(idx($index["lengths"], $key)) . "' title='" . lang('Length') . "'>" : "");
echo (support("descidx") ? checkbox("indexes[$j][descs][$i]", 1, idx($index["descs"], $key), lang('descending')) : "");
echo "</span> </span>";
$i++;
}
echo "<td><input name='indexes[$j][name]' value='" . h($index["name"]) . "' autocapitalize='off' aria-labelledby='label-name'>\n";
echo "<td><input type='image' class='icon' name='drop_col[$j]' src='../adminer/static/cross.gif' alt='x' title='" . lang('Remove') . "'>" . script("qsl('input').onclick = partial(editingRemoveRow, 'indexes\$1[type]');");
echo "<td>" . icon("cross", "drop_col[$j]", "x", lang('Remove')) . script("qsl('button').onclick = partial(editingRemoveRow, 'indexes\$1[type]');");
}
$j++;
}

View File

@@ -1,7 +1,7 @@
<?php
namespace Adminer;
$translations = array(
Lang::$translations = array(
'Login' => 'تسجيل الدخول',
'Logout successful.' => 'تم تسجيل الخروج بنجاح.',
'Invalid credentials.' => 'بيانات الدخول غير صالحة.',

View File

@@ -1,7 +1,7 @@
<?php
namespace Adminer;
$translations = array(
Lang::$translations = array(
// label for database system selection (MySQL, SQLite, ...)
'System' => 'Система',
'Server' => 'Сървър',

View File

@@ -1,7 +1,7 @@
<?php
namespace Adminer;
$translations = array(
Lang::$translations = array(
'Login' => 'লগইন',
'Logout successful.' => 'সফলভাবে লগআউট হয়েছে।',
'Invalid credentials.' => 'ভুল পাসওয়ার্ড।',

View File

@@ -1,7 +1,7 @@
<?php
namespace Adminer;
$translations = array(
Lang::$translations = array(
// label for database system selection (MySQL, SQLite, ...)
'System' => 'Sistem',
'Server' => 'Server',

View File

@@ -1,7 +1,7 @@
<?php
namespace Adminer;
$translations = array(
Lang::$translations = array(
'Login' => 'Inicia la sessió',
'Logout successful.' => 'Desconnexió correcta.',
'Invalid credentials.' => 'Credencials invàlides.',

View File

@@ -1,7 +1,7 @@
<?php
namespace Adminer;
$translations = array(
Lang::$translations = array(
// label for database system selection (MySQL, SQLite, ...)
'System' => 'Systém',
'Server' => 'Server',

View File

@@ -1,7 +1,7 @@
<?php
namespace Adminer;
$translations = array(
Lang::$translations = array(
'System' => 'System',
'Server' => 'Server',
'Username' => 'Brugernavn',

View File

@@ -1,7 +1,7 @@
<?php
namespace Adminer;
$translations = array(
Lang::$translations = array(
'Login' => 'Login',
'Logout successful.' => 'Abmeldung erfolgreich.',
'Thanks for using Adminer, consider <a href="https://www.adminer.org/en/donation/">donating</a>.' => 'Danke, dass Sie Adminer genutzt haben. <a href="https://www.adminer.org/de/donation/">Spenden willkommen!</a>.',
@@ -294,7 +294,7 @@ $translations = array(
'DB' => 'DB',
'ATTACH queries are not supported.' => 'ATTACH Abfragen werden nicht unterstützt.',
'Warnings' => 'Warnungen',
'Adminer does not support accessing a database without a password, <a href="https://www.adminer.org/en/password/"%s>more information</a>.' => 'Adminer unterstützt den Zugriff auf eine Datenbank ohne Passwort nicht, <a href="https://www.adminer.org/en/password/"%s>mehr Informationen</a>.',
'Adminer does not support accessing a database without a password, <a href="https://www.adminer.org/en/password/"%s>more information</a>.' => 'Adminer unterstützt den Zugriff auf eine Datenbank ohne Passwort nicht, <a href="https://www.adminer.org/de/password/"%s>mehr Informationen</a>.',
'Full table scan' => 'Vollständige Überprüfung der Tabelle',
'The action will be performed after successful login with the same credentials.' => 'Die Aktion wird nach erfolgreicher Anmeldung mit denselben Anmeldedaten ausgeführt.',
'Connecting to privileged ports is not allowed.' => 'Die Verbindung zu privilegierten Ports ist nicht erlaubt.',

View File

@@ -1,7 +1,7 @@
<?php
namespace Adminer;
$translations = array(
Lang::$translations = array(
// label for database system selection (MySQL, SQLite, ...)
'System' => 'Σύστημα',
'Server' => 'Διακομιστής',

View File

@@ -1,7 +1,7 @@
<?php
namespace Adminer;
$translations = array(
Lang::$translations = array(
'Too many unsuccessful logins, try again in %d minute(s).' => array('Too many unsuccessful logins, try again in %d minute.', 'Too many unsuccessful logins, try again in %d minutes.'),
'Query executed OK, %d row(s) affected.' => array('Query executed OK, %d row affected.', 'Query executed OK, %d rows affected.'),
'%d byte(s)' => array('%d byte', '%d bytes'),

View File

@@ -1,7 +1,7 @@
<?php
namespace Adminer;
$translations = array(
Lang::$translations = array(
'Login' => 'Login',
'Logout successful.' => 'Sesión finalizada con éxito.',
'Invalid credentials.' => 'Usuario y/o clave de acceso incorrecta.',

View File

@@ -1,7 +1,7 @@
<?php
namespace Adminer;
$translations = array(
Lang::$translations = array(
'Login' => 'Logi sisse',
'Logout successful.' => 'Väljalogimine õnnestus.',
'Invalid credentials.' => 'Ebakorrektsed andmed.',

View File

@@ -1,7 +1,7 @@
<?php
namespace Adminer;
$translations = array(
Lang::$translations = array(
// label for database system selection (MySQL, SQLite, ...)
'System' => 'سیستم',
'Server' => 'سرور',

View File

@@ -1,7 +1,7 @@
<?php
namespace Adminer;
$translations = array(
Lang::$translations = array(
// label for database system selection (MySQL, SQLite, ...)
'System' => 'Järjestelmä',
'Server' => 'Palvelin',

View File

@@ -1,7 +1,7 @@
<?php
namespace Adminer;
$translations = array(
Lang::$translations = array(
'Login' => 'Authentification',
'Logout successful.' => 'Au revoir !',
'Invalid credentials.' => 'Authentification échouée.',

View File

@@ -1,7 +1,7 @@
<?php
namespace Adminer;
$translations = array(
Lang::$translations = array(
'Login' => 'Conectar',
'Logout successful.' => 'Pechouse a sesión con éxito.',
'Invalid credentials.' => 'Credenciais (usuario e/ou contrasinal) inválidos.',

View File

@@ -1,7 +1,7 @@
<?php
namespace Adminer;
$translations = array(
Lang::$translations = array(
'Login' => 'התחברות',
'Logout successful.' => 'ההתחברות הצליחה',
'Invalid credentials.' => 'פרטי התחברות שגויים',

View File

@@ -1,7 +1,7 @@
<?php
namespace Adminer;
$translations = array(
Lang::$translations = array(
'Login' => 'Belépés',
'Logout successful.' => 'Sikeres kilépés.',
'Invalid credentials.' => 'Érvénytelen adatok.',

View File

@@ -1,7 +1,7 @@
<?php
namespace Adminer;
$translations = array(
Lang::$translations = array(
// label for database system selection (MySQL, SQLite, ...)
'System' => 'Sistem',
'Server' => 'Server',

View File

@@ -1,7 +1,7 @@
<?php
namespace Adminer;
$translations = array(
Lang::$translations = array(
'Login' => 'Autenticazione',
'Logout successful.' => 'Uscita effettuata con successo.',
'Invalid credentials.' => 'Credenziali non valide.',

View File

@@ -1,7 +1,7 @@
<?php
namespace Adminer;
$translations = array(
Lang::$translations = array(
'Login' => 'ログイン',
'Logout successful.' => 'ログアウトしました。',
'Invalid credentials.' => '不正なログインです。',
@@ -10,6 +10,8 @@ $translations = array(
'Password' => 'パスワード',
'Loaded plugins' => '読込済プラグイン',
'Thanks for using Adminer, consider <a href="https://www.adminer.org/en/donation/">donating</a>.' => 'Adminerのご利用ありがとうございました。(寄付は<a href="https://www.adminer.org/en/donation/">こちら</a>)',
'%s must <a%s>return an array</a>.' => '%s は<a%s>配列を返す</a>必要があります。',
'<a%s>Configure</a> %s in %s.' => '%2$s の %1$s <a%s>を設定してください</a>。',
'There is a space in the input password which might be the cause.' => '入力されたパスワードに空白が含まれているので、それが原因かもしれません。',
'Adminer does not support accessing a database without a password, <a href="https://www.adminer.org/en/password/"%s>more information</a>.' => 'Adminer はパスワードのないデータベースへの接続には対応していません。(<a href="https://www.adminer.org/en/password/"%s>詳細</a>)',
'Database does not support password.' => 'データベースがパスワードに対応していません。',
@@ -226,8 +228,8 @@ $translations = array(
'Stop on error' => 'エラーの場合は停止',
'Select data' => 'データ',
'%.3f s' => '%.3f 秒',
'$1-$3-$5' => '$1.$3.$5',
'[yyyy]-mm-dd' => '[yyyy].mm.dd',
'$1-$3-$5' => '$1/$3/$5',
'[yyyy]-mm-dd' => '[yyyy]/mm/dd',
'History' => '履歴',
'Variables' => '変数',
'Source and target columns must have the same data type, there must be an index on the target columns and referenced data must exist.' => 'ソースとターゲットの列は同じデータ型でなければなりません。ターゲット列に索引があり、データが存在しなければなりません。',

View File

@@ -1,7 +1,7 @@
<?php
namespace Adminer;
$translations = array(
Lang::$translations = array(
'Login' => 'შესვლა',
'Logout successful.' => 'გამოხვედით სისტემიდან.',
'Invalid credentials.' => 'არასწორი მომხმარებელი ან პაროლი.',

View File

@@ -1,7 +1,7 @@
<?php
namespace Adminer;
$translations = array(
Lang::$translations = array(
'$1-$3-$5' => '$1-$3-$5',
'%.3f s' => '%.3f 초',
'%d byte(s)' => '%d 바이트',

View File

@@ -1,7 +1,7 @@
<?php
namespace Adminer;
$translations = array(
Lang::$translations = array(
// label for database system selection (MySQL, SQLite, ...)
'System' => 'Sistema',
'Server' => 'Serveris',

View File

@@ -1,7 +1,7 @@
<?php
namespace Adminer;
$translations = array(
Lang::$translations = array(
'Login' => 'Ieiet',
'Logout successful.' => 'Jūs veiksmīgi izgājāt no sistēmas.',
'Invalid credentials.' => 'Nepareizs lietotāja vārds vai parole.',

View File

@@ -1,7 +1,7 @@
<?php
namespace Adminer;
$translations = array(
Lang::$translations = array(
// label for database system selection (MySQL, SQLite, ...)
'System' => 'Sistem',
'Server' => 'Pelayan',

View File

@@ -1,7 +1,7 @@
<?php
namespace Adminer;
$translations = array(
Lang::$translations = array(
'Login' => 'Aanmelden',
'Logout successful.' => 'Successvol afgemeld.',
'Invalid credentials.' => 'Ongeldige gebruikersgegevens.',

View File

@@ -1,7 +1,7 @@
<?php
namespace Adminer;
$translations = array(
Lang::$translations = array(
'System' => 'System',
'Server' => 'Server',
'Username' => 'Brukernavn',

View File

@@ -1,7 +1,7 @@
<?php
namespace Adminer;
$translations = array(
Lang::$translations = array(
// label for database system selection (MySQL, SQLite, ...)
'System' => 'Rodzaj bazy',
'Server' => 'Serwer',

View File

@@ -1,7 +1,7 @@
<?php
namespace Adminer;
$translations = array(
Lang::$translations = array(
'Login' => 'Entrar',
'Logout successful.' => 'Saída bem sucedida.',
'Invalid credentials.' => 'Identificação inválida.',

View File

@@ -1,7 +1,7 @@
<?php
namespace Adminer;
$translations = array(
Lang::$translations = array(
'Login' => 'Entrar',
'Logout successful.' => 'Sessão terminada com sucesso.',
'Invalid credentials.' => 'Identificação inválida.',

View File

@@ -1,7 +1,7 @@
<?php
namespace Adminer;
$translations = array(
Lang::$translations = array(
'Login' => 'Intră',
'Logout successful.' => 'Ați ieșit cu succes.',
'Invalid credentials.' => 'Numele de utilizator sau parola este greșită.',

View File

@@ -1,7 +1,7 @@
<?php
namespace Adminer;
$translations = array(
Lang::$translations = array(
'Login' => 'Войти',
'Logout successful.' => 'Вы успешно покинули систему.',
'Invalid credentials.' => 'Неправильное имя пользователя или пароль.',
@@ -302,6 +302,15 @@ $translations = array(
'Unknown error.' => 'Неизвестная ошибка.',
'Database does not support password.' => 'База данных не поддерживает пароль.',
'Disable %s or enable %s or %s extensions.' => 'Отключите %s или включите расширения %s или %s.',
'Check has been dropped.' => 'Проверка удалена.',
'Check has been altered.' => 'Проверка изменена.',
'Check has been created.' => 'Проверка создана.',
'Alter check' => 'Изменить проверку',
'Create check' => 'Создать проверку',
'Checks' => 'Проверки',
'Loaded plugins' => 'Загруженные плагины',
'%s must <a%s>return an array</a>.' => '%s должна <a%s>вернуть массив</a>.',
'<a%s>Configure</a> %s in %s.' => '<a%s>Настроить</a> %s в %s.',
);
// run `php ../../lang.php ru` to update this file

View File

@@ -1,7 +1,7 @@
<?php
namespace Adminer;
$translations = array(
Lang::$translations = array(
'Login' => 'Prihlásiť sa',
'Logout successful.' => 'Odhlásenie prebehlo v poriadku.',
'Invalid credentials.' => 'Neplatné prihlasovacie údaje.',
@@ -281,7 +281,7 @@ $translations = array(
'Warnings' => 'Varovania',
'%d / ' => '%d / ',
'Limit rows' => 'Limit riadkov',
'Adminer does not support accessing a database without a password, <a href="https://www.adminer.org/en/password/"%s>more information</a>.' => 'Adminer nepodporuje prístup k databáze bez hesla, <a href="https://www.adminer.org/cs/password/"%s>viac informácií</a>.',
'Adminer does not support accessing a database without a password, <a href="https://www.adminer.org/en/password/"%s>more information</a>.' => 'Adminer nepodporuje prístup k databáze bez hesla, <a href="https://www.adminer.org/sk/password/"%s>viac informácií</a>.',
'Default value' => 'Predvolená hodnota',
'Full table scan' => 'Prechod celej tabuľky',
'Too many unsuccessful logins, try again in %d minute(s).' => array('Príliš veľa pokusov o prihlásenie, skúste to znova za %d minutu.', 'Príliš veľa pokusov o prihlásenie, skúste to znova za %d minuty.', 'Príliš veľa pokusov o prihlásenie, skúste to znova za %d minút.'),

View File

@@ -1,7 +1,7 @@
<?php
namespace Adminer;
$translations = array(
Lang::$translations = array(
// label for database system selection (MySQL, SQLite, ...)
'System' => 'Sistem',
'Server' => 'Strežnik',

View File

@@ -1,7 +1,7 @@
<?php
namespace Adminer;
$translations = array(
Lang::$translations = array(
// label for database system selection (MySQL, SQLite, ...)
'System' => 'Систем',
'Server' => 'Сервер',

View File

@@ -1,7 +1,7 @@
<?php
namespace Adminer;
$translations = array(
Lang::$translations = array(
// label for database system selection (MySQL, SQLite, ...)
'System' => 'System',
'Server' => 'Server',

View File

@@ -1,7 +1,7 @@
<?php
namespace Adminer;
$translations = array(
Lang::$translations = array(
'Login' => 'நுழை',
'Logout successful.' => 'வெற்றிக‌ர‌மாய் வெளியேறியாயிற்று.',
'Invalid credentials.' => 'ச‌ரியான‌ விப‌ர‌ங்க‌ள் இல்லை.',

View File

@@ -1,7 +1,7 @@
<?php
namespace Adminer;
$translations = array(
Lang::$translations = array(
'Login' => 'เข้าสู่ระบบ',
'Logout successful.' => 'ออกจากระบบเรียบร้อยแล้ว.',
'Invalid credentials.' => 'ข้อมูลไม่ถูกต้อง.',

View File

@@ -1,7 +1,7 @@
<?php
namespace Adminer;
$translations = array(
Lang::$translations = array(
// label for database system selection (MySQL, SQLite, ...)
'System' => 'Sistem',
'Server' => 'Sunucu',
@@ -307,7 +307,7 @@ $translations = array(
// date format in Editor: $1 yyyy, $2 yy, $3 mm, $4 m, $5 dd, $6 d
'$1-$3-$5' => '$6.$4.$1',
// hint for date format - use language equivalents for day, month and year shortcuts
'[yyyy]-mm-dd' => '[yyyy]-aa-gg',
'[yyyy]-mm-dd' => 'g.a.[yyyy]',
// hint for time format - use language equivalents for hour, minute and second shortcuts
'HH:MM:SS' => 'SS:DD:ss',
'now' => 'şimdi',

View File

@@ -1,7 +1,7 @@
<?php
namespace Adminer;
$translations = array(
Lang::$translations = array(
// label for database system selection (MySQL, SQLite, ...)
'System' => 'Система Бази Даних',
'Server' => 'Сервер',
@@ -344,6 +344,18 @@ $translations = array(
'Saving' => 'Збереження',
'Unknown error.' => 'Невідома помилка.',
'Database does not support password.' => 'База даних не підтримує пароль.',
'Disable %s or enable %s or %s extensions.' => 'Вимкніть %s або увімкніть розширення %s або %s.',
'Check has been dropped.' => 'Перевірку видалено.',
'Check has been altered.' => 'Перевірка змінена.',
'Check has been created.' => 'Перевірку створено.',
'Alter check' => 'Змінити перевірку',
'Create check' => 'Створити перевірку',
'Vacuum' => 'Вакуум',
'%d / ' => '%d / ',
'Checks' => 'Перевірки',
'Loaded plugins' => 'Завантажені плагіни',
'%s must <a%s>return an array</a>.' => '%s має <a%s>повернути масив</a>.',
'<a%s>Configure</a> %s in %s.' => '<a%s>Налаштувати</a> %s у %s.',
);
// run `php ../../lang.php uk` to update this file

View File

@@ -1,7 +1,7 @@
<?php
namespace Adminer;
$translations = array(
Lang::$translations = array(
// label for database system selection (MySQL, SQLite, ...)
'System' => 'Tizim',
'Server' => 'Server',

View File

@@ -1,7 +1,7 @@
<?php
namespace Adminer;
$translations = array(
Lang::$translations = array(
// label for database system selection (MySQL, SQLite, ...)
'System' => 'Hệ thống',
'Server' => 'Máy chủ',

View File

@@ -1,7 +1,7 @@
<?php
namespace Adminer;
$translations = array(
Lang::$translations = array(
// label for database system selection (MySQL, SQLite, ...)
'System' => 'Xx',
'Server' => 'Xx',

View File

@@ -1,7 +1,7 @@
<?php
namespace Adminer;
$translations = array(
Lang::$translations = array(
// label for database system selection (MySQL, SQLite, ...)
'System' => '資料庫系統',
'Server' => '伺服器',

View File

@@ -1,7 +1,7 @@
<?php
namespace Adminer;
$translations = array(
Lang::$translations = array(
// label for database system selection (MySQL, SQLite, ...)
'System' => '系统',
'Server' => '服务器',

View File

@@ -5,11 +5,11 @@ page_header(lang('Privileges'));
echo '<p class="links"><a href="' . h(ME) . 'user=">' . lang('Create user') . "</a>";
$result = $connection->query("SELECT User, Host FROM mysql." . (DB == "" ? "user" : "db WHERE " . q(DB) . " LIKE Db") . " ORDER BY Host, User");
$result = connection()->query("SELECT User, Host FROM mysql." . (DB == "" ? "user" : "db WHERE " . q(DB) . " LIKE Db") . " ORDER BY Host, User");
$grant = $result;
if (!$result) {
// list logged user, information_schema.USER_PRIVILEGES lists just the current user too
$result = $connection->query("SELECT SUBSTRING_INDEX(CURRENT_USER, '@', 1) AS User, SUBSTRING_INDEX(CURRENT_USER, '@', -1) AS Host");
$result = connection()->query("SELECT SUBSTRING_INDEX(CURRENT_USER, '@', 1) AS User, SUBSTRING_INDEX(CURRENT_USER, '@', -1) AS Host");
}
echo "<form action=''><p>\n";

View File

@@ -3,6 +3,7 @@ namespace Adminer;
page_header(lang('Database schema'), "", array(), h(DB . ($_GET["ns"] ? ".$_GET[ns]" : "")));
/** @var array{float, float}[] */
$table_pos = array();
$table_pos_js = array();
$SCHEMA = ($_GET["schema"] ?: $_COOKIE["adminer_schema-" . str_replace(".", "_", DB)]); // $_COOKIE["adminer_schema"] was used before 3.2.0 //! ':' in table name
@@ -14,26 +15,28 @@ foreach ($matches as $i => $match) {
$top = 0;
$base_left = -1;
/** @var array{fields:Field[], pos:array{float, float}, references:string[][][]}[] */
$schema = array(); // table => array("fields" => array(name => field), "pos" => array(top, left), "references" => array(table => array(left => array(source, target))))
$referenced = array(); // target_table => array(table => array(left => target_column))
$lefts = array(); // float => bool
$all_fields = driver()->allFields();
foreach (table_status('', true) as $table => $table_status) {
if (is_view($table_status)) {
continue;
}
$pos = 0;
$schema[$table]["fields"] = array();
foreach (fields($table) as $name => $field) {
foreach ($all_fields[$table] as $field) {
$pos += 1.25;
$field["pos"] = $pos;
$schema[$table]["fields"][$name] = $field;
$schema[$table]["fields"][$field["field"]] = $field;
}
$schema[$table]["pos"] = ($table_pos[$table] ?: array($top, 0));
foreach ($adminer->foreignKeys($table) as $val) {
foreach (adminer()->foreignKeys($table) as $val) {
if (!$val["db"]) {
$left = $base_left;
if ($table_pos[$table][1] || $table_pos[$val["table"]][1]) {
$left = min(floatval($table_pos[$table][1]), floatval($table_pos[$val["table"]][1])) - 1;
if (idx($table_pos[$table], 1) || idx($table_pos[$val["table"]], 1)) {
$left = min(idx($table_pos[$table], 1, 0), idx($table_pos[$val["table"]], 1, 0)) - 1;
} else {
$base_left -= .1;
}
@@ -65,13 +68,13 @@ foreach ($schema as $name => $table) {
echo script("qsl('div').onmousedown = schemaMousedown;");
foreach ($table["fields"] as $field) {
$val = '<span' . type_class($field["type"]) . ' title="' . h($field["full_type"] . ($field["null"] ? " NULL" : '')) . '">' . h($field["field"]) . '</span>';
$val = '<span' . type_class($field["type"]) . ' title="' . h($field["type"] . ($field["length"] ? "($field[length])" : "") . ($field["null"] ? " NULL" : '')) . '">' . h($field["field"]) . '</span>';
echo "<br>" . ($field["primary"] ? "<i>$val</i>" : $val);
}
foreach ((array) $table["references"] as $target_name => $refs) {
foreach ($refs as $left => $ref) {
$left1 = $left - $table_pos[$name][1];
$left1 = $left - idx($table_pos[$name], 1);
$i = 0;
foreach ($ref[0] as $source) {
echo "\n<div class='references' title='" . h($target_name) . "' id='refs$left-" . ($i++) . "' style='left: $left1" . "em; top: " . $table["fields"][$source]["pos"] . "em; padding-top: .5em;'>"
@@ -83,11 +86,10 @@ foreach ($schema as $name => $table) {
foreach ((array) $referenced[$name] as $target_name => $refs) {
foreach ($refs as $left => $columns) {
$left1 = $left - $table_pos[$name][1];
$left1 = $left - idx($table_pos[$name], 1);
$i = 0;
foreach ($columns as $target) {
echo "\n<div class='references' title='" . h($target_name) . "' id='refd$left-" . ($i++) . "'"
. " style='left: $left1" . "em; top: " . $table["fields"][$target]["pos"] . "em; height: 1.25em; background: url(../adminer/static/arrow.gif) no-repeat right center;'>"
echo "\n<div class='references arrow' title='" . h($target_name) . "' id='refd$left-" . ($i++) . "' style='left: $left1" . "em; top: " . $table["fields"][$target]["pos"] . "em;'>"
. "<div style='height: .5em; border-bottom: 1px solid gray; width: " . (-$left1) . "em;'></div>"
. "</div>"
;

View File

@@ -36,10 +36,10 @@ if ($_GET["script"] == "db") {
json_row("");
} elseif ($_GET["script"] == "kill") {
$connection->query("KILL " . number($_POST["kill"]));
connection()->query("KILL " . number($_POST["kill"]));
} else { // connect
foreach (count_tables($adminer->databases()) as $db => $val) {
foreach (count_tables(adminer()->databases()) as $db => $val) {
json_row("tables-$db", $val);
json_row("size-$db", db_size($db));
}

View File

@@ -13,14 +13,14 @@ $rights = array(); // privilege => 0
$columns = array(); // selectable columns
$search_columns = array(); // searchable columns
$order_columns = array(); // searchable columns
$text_length = null;
$text_length = "";
foreach ($fields as $key => $field) {
$name = $adminer->fieldName($field);
$name = adminer()->fieldName($field);
$name_plain = html_entity_decode(strip_tags($name), ENT_QUOTES);
if (isset($field["privileges"]["select"]) && $name != "") {
$columns[$key] = $name_plain;
if (is_shortable($field)) {
$text_length = $adminer->selectLengthProcess();
$text_length = adminer()->selectLengthProcess();
}
}
if (isset($field["privileges"]["where"]) && $name != "") {
@@ -32,13 +32,13 @@ foreach ($fields as $key => $field) {
$rights += $field["privileges"];
}
list($select, $group) = $adminer->selectColumnsProcess($columns, $indexes);
list($select, $group) = adminer()->selectColumnsProcess($columns, $indexes);
$select = array_unique($select);
$group = array_unique($group);
$is_group = count($group) < count($select);
$where = $adminer->selectSearchProcess($fields, $indexes);
$order = $adminer->selectOrderProcess($fields, $indexes);
$limit = $adminer->selectLimitProcess();
$where = adminer()->selectSearchProcess($fields, $indexes);
$order = adminer()->selectOrderProcess($fields, $indexes);
$limit = adminer()->selectLimitProcess();
if ($_GET["val"] && is_ajax()) {
header("Content-Type: text/plain; charset=utf-8");
@@ -46,9 +46,9 @@ if ($_GET["val"] && is_ajax()) {
$as = convert_field($fields[key($row)]);
$select = array($as ?: idf_escape(key($row)));
$where[] = where_check($unique_idf, $fields);
$return = $driver->select($TABLE, $select, $where, $select);
$return = driver()->select($TABLE, $select, $where, $select);
if ($return) {
echo reset($return->fetch_row());
echo first($return->fetch_row());
}
}
exit;
@@ -85,7 +85,7 @@ if ($_POST && !$error) {
if ($_POST["export"]) {
save_settings(array("output" => $_POST["output"], "format" => $_POST["format"]), "adminer_import");
dump_headers($TABLE);
$adminer->dumpTable($TABLE, "");
adminer()->dumpTable($TABLE, "");
$from = ($select ? implode(", ", $select) : "*")
. convert_fields($columns, $fields, $select)
. "\nFROM " . table($TABLE);
@@ -99,12 +99,12 @@ if ($_POST && !$error) {
}
$query = implode(" UNION ALL ", $union);
}
$adminer->dumpData($TABLE, "table", $query);
$adminer->dumpFooter();
adminer()->dumpData($TABLE, "table", $query);
adminer()->dumpFooter();
exit;
}
if (!$adminer->selectEmailProcess($where, $foreign_keys)) {
if (!adminer()->selectEmailProcess($where, $foreign_keys)) {
if ($_POST["save"] || $_POST["delete"]) { // edit
$result = true;
$affected = 0;
@@ -118,33 +118,34 @@ if ($_POST && !$error) {
}
}
if ($_POST["delete"] || $set) {
if ($_POST["clone"]) {
$query = "INTO " . table($TABLE) . " (" . implode(", ", array_keys($set)) . ")\nSELECT " . implode(", ", $set) . "\nFROM " . table($TABLE);
}
$query = ($_POST["clone"] ? "INTO " . table($TABLE) . " (" . implode(", ", array_keys($set)) . ")\nSELECT " . implode(", ", $set) . "\nFROM " . table($TABLE) : "");
if ($_POST["all"] || ($primary && is_array($_POST["check"])) || $is_group) {
$result = ($_POST["delete"]
? $driver->delete($TABLE, $where_check)
? driver()->delete($TABLE, $where_check)
: ($_POST["clone"]
? queries("INSERT $query$where_check" . $driver->insertReturning($TABLE))
: $driver->update($TABLE, $set, $where_check)
? queries("INSERT $query$where_check" . driver()->insertReturning($TABLE))
: driver()->update($TABLE, $set, $where_check)
)
);
$affected = $connection->affected_rows + (is_object($result) ? $result->num_rows : 0); // PostgreSQL with RETURNING fills num_rows
$affected = connection()->affected_rows;
if (is_object($result)) { // PostgreSQL with RETURNING fills num_rows
$affected += $result->num_rows;
}
} else {
foreach ((array) $_POST["check"] as $val) {
// where is not unique so OR can't be used
$where2 = "\nWHERE " . ($where ? implode(" AND ", $where) . " AND " : "") . where_check($val, $fields);
$result = ($_POST["delete"]
? $driver->delete($TABLE, $where2, 1)
? driver()->delete($TABLE, $where2, 1)
: ($_POST["clone"]
? queries("INSERT" . limit1($TABLE, $query, $where2))
: $driver->update($TABLE, $set, $where2, 1)
: driver()->update($TABLE, $set, $where2, 1)
)
);
if (!$result) {
break;
}
$affected += $connection->affected_rows;
$affected += connection()->affected_rows;
}
}
}
@@ -158,7 +159,7 @@ if ($_POST && !$error) {
queries_redirect(remove_from_uri($_POST["all"] && $_POST["delete"] ? "page" : ""), $message, $result);
if (!$_POST["delete"]) {
$post_fields = (array) $_POST["fields"];
edit_form($TABLE, array_intersect_key($fields, $post_fields), $post_fields, !$_POST["clone"]);
edit_form($TABLE, array_intersect_key($fields, $post_fields), $post_fields, !$_POST["clone"], $error);
page_footer();
exit;
}
@@ -172,20 +173,20 @@ if ($_POST && !$error) {
foreach ($_POST["val"] as $unique_idf => $row) {
$set = array();
foreach ($row as $key => $val) {
$key = bracket_escape($key, 1); // 1 - back
$set[idf_escape($key)] = (preg_match('~char|text~', $fields[$key]["type"]) || $val != "" ? $adminer->processInput($fields[$key], $val) : "NULL");
$key = bracket_escape($key, true); // true - back
$set[idf_escape($key)] = (preg_match('~char|text~', $fields[$key]["type"]) || $val != "" ? adminer()->processInput($fields[$key], $val) : "NULL");
}
$result = $driver->update(
$result = driver()->update(
$TABLE,
$set,
" WHERE " . ($where ? implode(" AND ", $where) . " AND " : "") . where_check($unique_idf, $fields),
!$is_group && !$primary,
($is_group || $primary ? 0 : 1),
" "
);
if (!$result) {
break;
}
$affected += $connection->affected_rows;
$affected += connection()->affected_rows;
}
queries_redirect(remove_from_uri(), lang('%d item(s) have been affected.', $affected), $result);
}
@@ -200,7 +201,7 @@ if ($_POST && !$error) {
$cols = array_keys($fields);
preg_match_all('~(?>"[^"]*"|[^"\r\n]+)+~', $file, $matches);
$affected = count($matches[0]);
$driver->begin();
driver()->begin();
$separator = ($_POST["separator"] == "csv" ? "," : ($_POST["separator"] == "tsv" ? "\t" : ";"));
$rows = array();
foreach ($matches[0] as $key => $val) {
@@ -217,18 +218,18 @@ if ($_POST && !$error) {
$rows[] = $set;
}
}
$result = (!$rows || $driver->insertUpdate($TABLE, $rows, $primary));
$result = (!$rows || driver()->insertUpdate($TABLE, $rows, $primary));
if ($result) {
$driver->commit();
driver()->commit();
}
queries_redirect(remove_from_uri("page"), lang('%d row(s) have been imported.', $affected), $result);
$driver->rollback(); // after queries_redirect() to not overwrite error
driver()->rollback(); // after queries_redirect() to not overwrite error
}
}
}
$table_name = $adminer->tableName($table_status);
$table_name = adminer()->tableName($table_status);
if (is_ajax()) {
page_headers();
ob_start();
@@ -250,7 +251,7 @@ if (isset($rights["insert"]) || !support("table")) {
$set = $params ? "&" . http_build_query($params) : "";
}
$adminer->selectLinks($table_status, $set);
adminer()->selectLinks($table_status, $set);
if (!$columns && support("table")) {
echo "<p class='error'>" . lang('Unable to select the table') . ($fields ? "." : ": " . error()) . "\n";
@@ -261,18 +262,18 @@ if (!$columns && support("table")) {
echo (DB != "" ? input_hidden("db", DB) . (isset($_GET["ns"]) ? input_hidden("ns", $_GET["ns"]) : "") : ""); // not used in Editor
echo input_hidden("select", $TABLE);
echo "</div>\n";
$adminer->selectColumnsPrint($select, $columns);
$adminer->selectSearchPrint($where, $search_columns, $indexes);
$adminer->selectOrderPrint($order, $order_columns, $indexes);
$adminer->selectLimitPrint($limit);
$adminer->selectLengthPrint($text_length);
$adminer->selectActionPrint($indexes);
adminer()->selectColumnsPrint($select, $columns);
adminer()->selectSearchPrint($where, $search_columns, $indexes);
adminer()->selectOrderPrint($order, $order_columns, $indexes);
adminer()->selectLimitPrint($limit);
adminer()->selectLengthPrint($text_length);
adminer()->selectActionPrint($indexes);
echo "</form>\n";
$page = $_GET["page"];
if ($page == "last") {
$found_rows = get_val(count_rows($TABLE, $where, $is_group, $group));
$page = floor(max(0, $found_rows - 1) / $limit);
$page = floor(max(0, intval($found_rows) - 1) / $limit);
}
$select2 = $select;
@@ -298,7 +299,7 @@ if (!$columns && support("table")) {
}
}
}
$result = $driver->select($TABLE, $select2, $where, $group2, $order, $limit, $page, true);
$result = driver()->select($TABLE, $select2, $where, $group2, $order, $limit, $page, true);
if (!$result) {
echo "<p class='error'>" . error() . "\n";
@@ -317,14 +318,14 @@ if (!$columns && support("table")) {
}
// use count($rows) without LIMIT, COUNT(*) without grouping, FOUND_ROWS otherwise (slowest)
if ($_GET["page"] != "last" && $limit != "" && $group && $is_group && JUSH == "sql") {
if ($_GET["page"] != "last" && $limit && $group && $is_group && JUSH == "sql") {
$found_rows = get_val(" SELECT FOUND_ROWS()"); // space to allow mysql.trace_mode
}
if (!$rows) {
echo "<p class='message'>" . lang('No rows.') . "\n";
} else {
$backward_keys = $adminer->backwardKeys($TABLE, $table_name);
$backward_keys = adminer()->backwardKeys($TABLE, $table_name);
echo "<div class='scrollable'>";
echo "<table id='table' class='nowrap checkable odds'>";
@@ -339,18 +340,19 @@ if (!$columns && support("table")) {
$rank = 1;
foreach ($rows[0] as $key => $val) {
if (!isset($unselected[$key])) {
$val = $_GET["columns"][key($select)];
/** @var array{fun?:string, col?:string} */
$val = idx($_GET["columns"], key($select)) ?: array();
$field = $fields[$select ? ($val ? $val["col"] : current($select)) : $key];
$name = ($field ? $adminer->fieldName($field, $rank) : ($val["fun"] ? "*" : h($key)));
$name = ($field ? adminer()->fieldName($field, $rank) : ($val["fun"] ? "*" : h($key)));
if ($name != "") {
$rank++;
$names[$key] = $name;
$column = idf_escape($key);
$href = remove_from_uri('(order|desc)[^=]*|page') . '&order%5B0%5D=' . urlencode($key);
$desc = "&desc%5B0%5D=1";
$sortable = isset($field["privileges"]["order"]);
echo "<th id='th[" . h(bracket_escape($key)) . "]'>" . script("mixin(qsl('th'), {onmouseover: partial(columnMouse), onmouseout: partial(columnMouse, ' hidden')});", "");
$fun = apply_sql_function($val["fun"], $name); //! columns looking like functions
$sortable = isset($field["privileges"]["order"]) || $fun;
echo ($sortable ? '<a href="' . h($href . ($order[0] == $column || $order[0] == $key || (!$order && $is_group && $group[0] == $column) ? $desc : '')) . '">' . "$fun</a>" : $fun); // $order[0] == $key - COUNT(*)
echo "<span class='column hidden'>";
if ($sortable) {
@@ -382,7 +384,7 @@ if (!$columns && support("table")) {
ob_end_clean();
}
foreach ($adminer->rowDescriptions($rows, $foreign_keys) as $n => $row) {
foreach (adminer()->rowDescriptions($rows, $foreign_keys) as $n => $row) {
$unique_array = unique_array($rows[$n], $indexes);
if (!$unique_array) {
$unique_array = array();
@@ -394,9 +396,10 @@ if (!$columns && support("table")) {
}
$unique_idf = "";
foreach ($unique_array as $key => $val) {
if ((JUSH == "sql" || JUSH == "pgsql") && preg_match('~char|text|enum|set~', $fields[$key]["type"]) && strlen($val) > 64) {
$field = (array) $fields[$key];
if ((JUSH == "sql" || JUSH == "pgsql") && preg_match('~char|text|enum|set~', $field["type"]) && strlen($val) > 64) {
$key = (strpos($key, '(') ? $key : idf_escape($key)); //! columns looking like functions
$key = "MD5(" . (JUSH != 'sql' || preg_match("~^utf8~", $fields[$key]["collation"]) ? $key : "CONVERT($key USING " . charset($connection) . ")") . ")";
$key = "MD5(" . (JUSH != 'sql' || preg_match("~^utf8~", $field["collation"]) ? $key : "CONVERT($key USING " . charset(connection()) . ")") . ")";
$val = md5($val);
}
$unique_idf .= "&" . ($val !== null ? urlencode("where[" . bracket_escape($key) . "]") . "=" . urlencode($val === false ? "f" : $val) : "null%5B%5D=" . urlencode($key));
@@ -408,8 +411,8 @@ if (!$columns && support("table")) {
foreach ($row as $key => $val) {
if (isset($names[$key])) {
$field = $fields[$key];
$val = $driver->value($val, $field);
$field = (array) $fields[$key];
$val = driver()->value($val, $field);
if ($val != "" && (!isset($email_fields[$key]) || $email_fields[$key] != "")) {
$email_fields[$key] = (is_mail($val) ? $names[$key] : ""); //! filled e-mails can be contained on other pages
}
@@ -450,10 +453,10 @@ if (!$columns && support("table")) {
$val = select_value($val, $link, $field, $text_length);
$id = h("val[$unique_idf][" . bracket_escape($key) . "]");
$value = $_POST["val"][$unique_idf][bracket_escape($key)];
$value = idx(idx($_POST["val"], $unique_idf), bracket_escape($key));
$editable = !is_array($row[$key]) && is_utf8($val) && $rows[$n][$key] == $row[$key] && !$functions[$key] && !$field["generated"];
$text = preg_match('~text|json|lob~', $field["type"]);
echo "<td id='$id'" . (preg_match(number_type(), $field["type"]) && is_numeric(strip_tags($val)) ? " class='number'" : "");
echo "<td id='$id'" . (preg_match(number_type(), $field["type"]) && ($val == '<i>NULL</i>' || is_numeric(strip_tags($val))) ? " class='number'" : "");
if (($_GET["modify"] && $editable) || $value !== null) {
$h_value = h($value !== null ? $value : $row[$key]);
echo ">" . ($text ? "<textarea name='$id' cols='30' rows='" . (substr_count($row[$key], "\n") + 1) . "'>$h_value</textarea>" : "<input name='$id' value='$h_value' size='$lengths[$key]'>");
@@ -470,7 +473,7 @@ if (!$columns && support("table")) {
if ($backward_keys) {
echo "<td>";
}
$adminer->backwardKeysPrint($backward_keys, $rows[$n]);
adminer()->backwardKeysPrint($backward_keys, $rows[$n]);
echo "</tr>\n"; // close to allow white-space: pre
}
@@ -484,12 +487,13 @@ if (!$columns && support("table")) {
if (!is_ajax()) {
if ($rows || $page) {
$exact_count = true;
$found_rows = null;
if ($_GET["page"] != "last") {
if ($limit == "" || (count($rows) < $limit && ($rows || !$page))) {
if (!$limit || (count($rows) < $limit && ($rows || !$page))) {
$found_rows = ($page ? $page * $limit : 0) + count($rows);
} elseif (JUSH != "sql" || !$is_group) {
$found_rows = ($is_group ? false : found_rows($table_status, $where));
if ($found_rows < max(1e4, 2 * ($page + 1) * $limit)) {
if (intval($found_rows) < max(1e4, 2 * ($page + 1) * $limit)) {
// slow with big tables
$found_rows = first(slow_query(count_rows($TABLE, $where, $is_group, $group)));
} else {
@@ -498,19 +502,17 @@ if (!$columns && support("table")) {
}
}
$pagination = ($limit != "" && ($found_rows === false || $found_rows > $limit || $page));
$pagination = ($limit && ($found_rows === false || $found_rows > $limit || $page));
if ($pagination) {
echo (($found_rows === false ? count($rows) + 1 : $found_rows - $page * $limit) > $limit
? '<p><a href="' . h(remove_from_uri("page") . "&page=" . ($page + 1)) . '" class="loadmore">' . lang('Load more data') . '</a>'
. script("qsl('a').onclick = partial(selectLoadMore, " . (+$limit) . ", '" . lang('Loading') . "…');", "")
. script("qsl('a').onclick = partial(selectLoadMore, $limit, '" . lang('Loading') . "…');", "")
: ''
);
echo "\n";
}
}
echo "<div class='footer'><div>\n";
if ($rows || $page) {
echo "<div class='footer'><div>\n";
if ($pagination) {
// display first, previous 4, next 4 and last page
$max_page = ($found_rows === false
@@ -548,7 +550,7 @@ if (!$columns && support("table")) {
echo checkbox("all", 1, 0, ($found_rows !== false ? ($exact_count ? "" : "~ ") . lang('%d row(s)', $found_rows) : ""), $onclick) . "\n";
echo "</fieldset>\n";
if ($adminer->selectCommandPrint()) {
if (adminer()->selectCommandPrint()) {
?>
<fieldset<?php echo ($_GET["modify"] ? '' : ' class="jsonly"'); ?>><legend><?php echo lang('Modify'); ?></legend><div>
<input type="submit" value="<?php echo lang('Save'); ?>"<?php echo ($_GET["modify"] ? '' : ' title="' . lang('Ctrl+click on a value to modify it.') . '"'); ?>>
@@ -561,7 +563,7 @@ if (!$columns && support("table")) {
<?php
}
$format = $adminer->dumpFormat();
$format = adminer()->dumpFormat();
foreach ((array) $_GET["columns"] as $column) {
if ($column["fun"]) {
unset($format['sql']);
@@ -570,19 +572,18 @@ if (!$columns && support("table")) {
}
if ($format) {
print_fieldset("export", lang('Export') . " <span id='selected2'></span>");
$output = $adminer->dumpOutput();
$output = adminer()->dumpOutput();
echo ($output ? html_select("output", $output, $adminer_import["output"]) . " " : "");
echo html_select("format", $format, $adminer_import["format"]);
echo " <input type='submit' name='export' value='" . lang('Export') . "'>\n";
echo "</div></fieldset>\n";
}
$adminer->selectEmailPrint(array_filter($email_fields, 'strlen'), $columns);
adminer()->selectEmailPrint(array_filter($email_fields, 'strlen'), $columns);
echo "</div></div>\n";
}
echo "</div></div>\n";
if ($adminer->selectImportPrint()) {
if (adminer()->selectImportPrint()) {
echo "<div>";
echo "<a href='#import'>" . lang('Import') . "</a>";
echo script("qsl('a').onclick = partial(toggle, 'import');", "");

View File

@@ -4,9 +4,9 @@ namespace Adminer;
if (!$error && $_POST["export"]) {
save_settings(array("output" => $_POST["output"], "format" => $_POST["format"]), "adminer_import");
dump_headers("sql");
$adminer->dumpTable("", "");
$adminer->dumpData("", "table", $_POST["query"]);
$adminer->dumpFooter();
adminer()->dumpTable("", "");
adminer()->dumpData("", "table", $_POST["query"]);
adminer()->dumpFooter();
exit;
}
@@ -17,6 +17,7 @@ if (!$error && $_POST["clear"]) {
$history = array();
redirect(remove_from_uri("history"));
}
stop_session();
page_header((isset($_GET["import"]) ? lang('Import') : lang('SQL command')), $error);
@@ -25,7 +26,7 @@ if (!$error && $_POST) {
if (!isset($_GET["import"])) {
$query = $_POST["query"];
} elseif ($_POST["webfile"]) {
$sql_file_path = $adminer->importServerPath();
$sql_file_path = adminer()->importServerPath();
$fp = @fopen((file_exists($sql_file_path)
? $sql_file_path
: "compress.zlib://$sql_file_path.gz"
@@ -37,7 +38,7 @@ if (!$error && $_POST) {
if (is_string($query)) { // get_file() returns error as number, fread() as false
if (function_exists('memory_get_usage') && ($memory_limit = ini_bytes("memory_limit")) != "-1") {
@ini_set("memory_limit", max($memory_limit, 2 * strlen($query) + memory_get_usage() + 8e6)); // @ - may be disabled, 2 - substr and trim, 8e6 - other variables
@ini_set("memory_limit", max($memory_limit, strval(2 * strlen($query) + memory_get_usage() + 8e6))); // @ - may be disabled, 2 - substr and trim, 8e6 - other variables
}
if ($query != "" && strlen($query) < 1e6) { // don't add big queries
@@ -54,8 +55,8 @@ if (!$error && $_POST) {
$delimiter = ";";
$offset = 0;
$empty = true;
$connection2 = connect($adminer->credentials()); // connection for exploring indexes and EXPLAIN (to not replace FOUND_ROWS()) //! PDO - silent error
if (is_object($connection2) && DB != "") {
$connection2 = connect(adminer()->credentials()); // connection for exploring indexes and EXPLAIN (to not replace FOUND_ROWS()) //! PDO - silent error
if ($connection2 && DB != "") {
$connection2->select_db(DB);
if ($_GET["ns"] != "") {
set_schema($_GET["ns"], $connection2);
@@ -66,7 +67,7 @@ if (!$error && $_POST) {
$parse = '[\'"' . (JUSH == "sql" ? '`#' : (JUSH == "sqlite" ? '`[' : (JUSH == "mssql" ? '[' : ''))) . ']|/\*|-- |$' . (JUSH == "pgsql" ? '|\$[^$]*\$' : '');
$total_start = microtime(true);
$adminer_export = get_settings("adminer_import"); // this doesn't offer SQL export so we match the import/export style at select
$dump_format = $adminer->dumpFormat();
$dump_format = adminer()->dumpFormat();
unset($dump_format["sql"]);
while ($query != "") {
@@ -85,7 +86,7 @@ if (!$error && $_POST) {
$offset = $pos + strlen($found);
if ($found && rtrim($found) != $delimiter) { // find matching quote or comment end
$c_style_escapes = $driver->hasCStyleEscapes() || (JUSH == "pgsql" && ($pos > 0 && strtolower($query[$pos - 1]) == "e"));
$c_style_escapes = driver()->hasCStyleEscapes() || (JUSH == "pgsql" && ($pos > 0 && strtolower($query[$pos - 1]) == "e"));
$pattern = ($found == '/*' ? '\*/'
: ($found == '[' ? ']'
@@ -109,7 +110,7 @@ if (!$error && $_POST) {
$empty = false;
$q = substr($query, 0, $pos);
$commands++;
$print = "<pre id='sql-$commands'><code class='jush-" . JUSH . "'>" . $adminer->sqlCommandQuery($q) . "</code></pre>\n";
$print = "<pre id='sql-$commands'><code class='jush-" . JUSH . "'>" . adminer()->sqlCommandQuery($q) . "</code></pre>\n";
if (JUSH == "sqlite" && preg_match("~^$space*+ATTACH\\b~i", $q, $match)) {
// PHP doesn't support setting SQLITE_LIMIT_ATTACHED
echo $print;
@@ -126,16 +127,16 @@ if (!$error && $_POST) {
}
$start = microtime(true);
//! don't allow changing of character_set_results, convert encoding of displayed query
if ($connection->multi_query($q) && is_object($connection2) && preg_match("~^$space*+USE\\b~i", $q)) {
if (connection()->multi_query($q) && $connection2 && preg_match("~^$space*+USE\\b~i", $q)) {
$connection2->query($q);
}
do {
$result = $connection->store_result();
$result = connection()->store_result();
if ($connection->error) {
if (connection()->error) {
echo ($_POST["only_errors"] ? $print : "");
echo "<p class='error'>" . lang('Error in query') . ($connection->errno ? " ($connection->errno)" : "") . ": " . error() . "\n";
echo "<p class='error'>" . lang('Error in query') . (connection()->errno ? " (" . connection()->errno . ")" : "") . ": " . error() . "\n";
$errors[] = " <a href='#sql-$commands'>$commands</a>";
if ($_POST["error_stops"]) {
break 2;
@@ -145,17 +146,18 @@ if (!$error && $_POST) {
$time = " <span class='time'>(" . format_time($start) . ")</span>"
. (strlen($q) < 1000 ? " <a href='" . h(ME) . "sql=" . urlencode(trim($q)) . "'>" . lang('Edit') . "</a>" : "") // 1000 - maximum length of encoded URL in IE is 2083 characters
;
$affected = $connection->affected_rows; // getting warnings overwrites this
$warnings = ($_POST["only_errors"] ? "" : $driver->warnings());
$affected = connection()->affected_rows; // getting warnings overwrites this
$warnings = ($_POST["only_errors"] ? "" : driver()->warnings());
$warnings_id = "warnings-$commands";
if ($warnings) {
$time .= ", <a href='#$warnings_id'>" . lang('Warnings') . "</a>" . script("qsl('a').onclick = partial(toggle, '$warnings_id');", "");
}
$explain = null;
$orgtables = null;
$explain_id = "explain-$commands";
if (is_object($result)) {
$limit = $_POST["limit"];
$orgtables = select($result, $connection2, array(), $limit);
$orgtables = print_select_result($result, $connection2, array(), $limit);
if (!$_POST["only_errors"]) {
echo "<form action='' method='post'>\n";
$num_rows = $result->num_rows;
@@ -166,7 +168,7 @@ if (!$error && $_POST) {
}
$id = "export-$commands";
echo ", <a href='#$id'>" . lang('Export') . "</a>" . script("qsl('a').onclick = partial(toggle, '$id');", "") . "<span id='$id' class='hidden'>: "
. html_select("output", $adminer->dumpOutput(), $adminer_export["output"]) . " "
. html_select("output", adminer()->dumpOutput(), $adminer_export["output"]) . " "
. html_select("format", $dump_format, $adminer_export["format"])
. input_hidden("query", $q)
. "<input type='submit' name='export' value='" . lang('Export') . "'>" . input_token() . "</span>\n"
@@ -181,19 +183,19 @@ if (!$error && $_POST) {
stop_session();
}
if (!$_POST["only_errors"]) {
echo "<p class='message' title='" . h(isset($connection->info) ? $connection->info : "") . "'>" . lang('Query executed OK, %d row(s) affected.', $affected) . "$time\n";
echo "<p class='message' title='" . h(connection()->info) . "'>" . lang('Query executed OK, %d row(s) affected.', $affected) . "$time\n";
}
}
echo ($warnings ? "<div id='$warnings_id' class='hidden'>\n$warnings</div>\n" : "");
if ($explain) {
echo "<div id='$explain_id' class='hidden explain'>\n";
select($explain, $connection2, $orgtables);
print_select_result($explain, $connection2, $orgtables);
echo "</div>\n";
}
}
$start = microtime(true);
} while ($connection->next_result());
} while (connection()->next_result());
}
$query = substr($query, $offset);
@@ -230,13 +232,13 @@ if (!isset($_GET["import"])) {
} elseif ($_GET["history"] == "all") {
$q = $history;
} elseif ($_GET["history"] != "") {
$q = $history[$_GET["history"]][0];
$q = idx($history[$_GET["history"]], 0);
}
echo "<p>";
textarea("query", $q, 20);
echo script(($_POST ? "" : "qs('textarea').focus();\n") . "qs('#form').onsubmit = partial(sqlSubmit, qs('#form'), '" . js_escape(remove_from_uri("sql|limit|error_stops|only_errors|history")) . "');");
echo "<p>";
$adminer->sqlPrintAfter();
adminer()->sqlPrintAfter();
echo "$execute\n";
echo lang('Limit rows') . ": <input type='number' name='limit' class='size' value='" . h($_POST ? $_POST["limit"] : $_GET["limit"]) . "'>\n";
@@ -248,7 +250,7 @@ if (!isset($_GET["import"])) {
: lang('File uploads are disabled.')
);
echo "</div></fieldset>\n";
$importServerPath = $adminer->importServerPath();
$importServerPath = adminer()->importServerPath();
if ($importServerPath) {
echo "<fieldset><legend>" . lang('From server') . "</legend><div>";
echo lang('Webserver file %s', "<code>" . h($importServerPath) . "$gz</code>");

View File

@@ -1,8 +1,7 @@
<?php
function adminer_object() {
include_once "../plugins/plugin.php";
include_once "../plugins/login-password-less.php";
return new AdminerPlugin(array(
return new Adminer\Plugins(array(
// TODO: inline the result of password_hash() so that the password is not visible in source codes
new AdminerLoginPasswordLess(password_hash("YOUR_PASSWORD_HERE", PASSWORD_DEFAULT)),
));

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 B

View File

@@ -1,6 +1,10 @@
/** @author Robert Mesaros, https://www.rmsoft.sk */
body { color: #829bb0; background: #002240; }
html {
--bg: #002240;
--fg: #829bb0;
}
a { color: #517fa8; }
a:visited { color: #517fa8; }
a:link:hover, a:visited:hover { color: #9bc0e1; }
@@ -21,7 +25,6 @@ input.required, input.maxlength { box-shadow: 1px 1px 1px red; }
.error { color: red; background: #efdada; border: 1px solid #e76f6f; }
.error b { background: #efeaea; }
.message { color: #0b860b; background: #efe; border: 1px solid #7fbd7f; }
.message table { color: #829bb0; background: #002240; }
.char { color: #a949a9; }
.date { color: #59c159; }
.enum { color: #d55c5c; }
@@ -30,12 +33,10 @@ input.required, input.maxlength { box-shadow: 1px 1px 1px red; }
.js .checkable .checked td, .js .checkable .checked th { background: #10395c; color: #67a4a5; }
.js .checkable .checked:hover td, .js .checkable .checked:hover th { background: #133553; }
.js .checkable .checked a { color: #67a4a5; }
.icon { background-color: #062642; }
.icon { filter: invert(1); background-color: #062642; }
.icon:hover { background-color: #d1394e; }
.footer { border-top-color: rgba(0, 34, 64, .7); border-image-source: linear-gradient(rgba(0, 34, 64, 0.2), #002240); }
.footer > div { background: #002240; }
#menu { border-color: #a3bdd3; }
#menu p, #logins, #tables { border-color: #326b9c; }
#logins a, #tables a, #tables span { background: #002240; }
#breadcrumb { background: #154269; }
#h1 { color: #ffddbf; }
#version { color: #d2b397; }

View File

@@ -1,13 +1,18 @@
/** @author Ondrej Valka, http://valka.info */
body { color: #000; background: #fff; font: 90%/1.25 Verdana, Arial, Helvetica, sans-serif; margin: 0; min-width: fit-content; }
html {
--bg: #fff;
--fg: #000;
}
body { color: var(--fg); background: var(--bg); font: 90%/1.25 Verdana, Arial, Helvetica, sans-serif; margin: 0; min-width: fit-content; }
a { color: blue; text-decoration: none; }
a:visited { color: navy; }
a:link:hover, a:visited:hover { color: red; text-decoration: underline; }
a.text:hover { text-decoration: none; }
a.jush-help:hover { color: inherit; }
h1 { font-size: 150%; margin: 0; padding: .8em 1em; border-bottom: 1px solid #999; font-weight: normal; color: #777; background: #eee; }
h2 { font-size: 150%; margin: 0 0 20px -18px; padding: .8em 1em; border-bottom: 1px solid #000; color: #000; font-weight: normal; background: #ddf; }
h2 { font-size: 150%; margin: 0 0 20px -18px; padding: .8em 1em; border-bottom: 1px solid var(--fg); font-weight: normal; background: #ddf; }
h3 { font-weight: normal; font-size: 130%; margin: 1em 0 0; }
form { margin: 0; }
td table { width: 100%; margin: 0; }
@@ -27,7 +32,7 @@ code { font-size: 110%; padding: 1px 2px; background: #eee; }
pre { margin: 1em 0 0; }
td pre { margin: 0; }
pre, textarea { font: 110%/1.25 monospace; }
pre.jush { background: #fff; }
pre.jush { background: var(--bg); }
pre code { display: block; font-size: 100%; }
input, textarea { box-sizing: border-box; }
input, select { vertical-align: middle; }
@@ -42,9 +47,9 @@ input.wayoff { left: -1000px; position: absolute; }
.nowrap td, .nowrap th, td.nowrap, p.nowrap { white-space: pre; }
.wrap td { white-space: normal; }
.error { color: red; background: #fee; }
.error b { background: #fff; font-weight: normal; }
.error b { background: var(--bg); font-weight: normal; }
.message { color: green; background: #efe; }
.message table { color: #000; background: #fff; }
.message table { color: var(--fg); background: var(--bg); }
.error, .message { padding: .5em .8em; margin: 1em 20px 0 0; }
.char { color: #007F00; }
.date { color: #7F007F; }
@@ -61,12 +66,13 @@ input.wayoff { left: -1000px; position: absolute; }
.sqlarea { width: 98%; }
.sql-footer { margin-bottom: 2.5em; }
.explain table { white-space: pre; }
.icon { width: 18px; height: 18px; background-color: navy; }
.icon { width: 18px; height: 18px; background-color: navy; border: 0; vertical-align: middle; }
.icon span { display: none; }
.icon:hover { background-color: red; }
.size { width: 7ex; }
.help { cursor: help; }
.footer { position: sticky; bottom: 0; margin-right: -20px; border-top: 20px solid rgba(255, 255, 255, .7); border-image: linear-gradient(rgba(255, 255, 255, .2), #fff) 100% 0; }
.footer > div { background: #fff; padding: 0 0 .5em; }
.footer { position: sticky; bottom: 0; margin-right: -20px; border-top: 20px solid rgb(from var(--bg) r g b / .7); border-image: linear-gradient(rgb(from var(--bg) r g b / .2), var(--bg)) 100% 0; }
.footer > div { background: var(--bg); padding: 0 0 .5em; }
.footer fieldset { margin-top: 0; }
.links a { white-space: nowrap; margin-right: 20px; }
.logout { margin-top: .5em; position: absolute; top: 0; right: 0; }
@@ -77,9 +83,10 @@ input.wayoff { left: -1000px; position: absolute; }
#logins li, #tables li { list-style: none; }
#dbs { overflow: hidden; }
#logins, #tables { white-space: nowrap; overflow: hidden; }
#logins a, #tables a, #tables span { background: #fff; }
#logins a, #tables a, #tables span { background: var(--bg); }
#content { margin: 2em 0 0 21em; padding: 10px 20px 20px 0; }
#lang { position: absolute; top: -2.6em; left: 0; padding: .3em 1em; }
#menuopen { display: none; }
#breadcrumb { white-space: nowrap; position: absolute; top: 0; left: 21em; background: #eee; height: 2em; line-height: 1.8em; padding: 0 1em; margin: 0 0 0 -18px; }
#h1 { color: #777; text-decoration: none; font-style: italic; }
#version { color: red; }
@@ -88,6 +95,14 @@ input.wayoff { left: -1000px; position: absolute; }
#schema .references { position: absolute; }
#help { position: absolute; border: 1px solid #999; background: #eee; padding: 5px; font-family: monospace; z-index: 1; }
/* inlined here and not in compile.php because otherwise the development version flickers a little bit when loading the images */
.icon-up { background-image: url(data:image/gif;base64,R0lGODlhEgASAIEAMe7u7gAAgJmZmQAAACH5BAEAAAEALAAAAAASABIAAQIghI+py+0PTQhRTgrvfRP0nmEVOIoReZphxbauAMfyHBcAOw==); }
.icon-down { background-image: url(data:image/gif;base64,R0lGODlhEgASAIEAMe7u7gAAgJmZmQAAACH5BAEAAAEALAAAAAASABIAAQIghI+py+0PTQjxzCopvltX/lyix0wm2ZwdxraVAMfyHBcAOw==); }
.icon-plus { background-image: url(data:image/gif;base64,R0lGODlhEgASAIEAMe7u7gAAgJmZmQAAACH5BAEAAAEALAAAAAASABIAAQIhhI+py+0PTQjxzCopvm/6rykgCHGVGaFliLXuI8TyTMsFADs=); }
.icon-cross { background-image: url(data:image/gif;base64,R0lGODlhEgASAIEAMe7u7gAAgJmZmQAAACH5BAEAAAEALAAAAAASABIAAQIjhI+py+0PIwph1kZvfnnDLoFfd2GU4THnsUruC0fCTNc2XQAAOw==); }
.icon-move { background-image: url(data:image/gif;base64,R0lGODlhEgASAJEAAO7u7gAAAJmZmQAAACH5BAEAAAEALAAAAAASABIAAAIfhI+py+3vgpyU0Rug3gnX5U3cqIWSZZLqigjuC8dvAQA7); }
#schema .arrow { height: 1.25em; background: url(data:image/gif;base64,R0lGODlhCAAKAIAAAICAgP///yH5BAEAAAEALAAAAAAIAAoAAAIPBIJplrGLnpQRqtOy3rsAADs=) no-repeat right center; }
.rtl h2 { margin: 0 -18px 20px 0; }
.rtl p, .rtl table, .rtl .error, .rtl .message { margin: 1em 0 0 20px; }
.rtl .logout { left: 0; right: auto; }
@@ -100,11 +115,14 @@ input.wayoff { left: -1000px; position: absolute; }
@media all and (max-width: 880px) {
.pages { left: auto; }
.logout { position: static; padding: 1em; }
#menu { position: static; width: auto; }
.logout { padding: 1em; top: 3em; }
#menu { width: auto; background: var(--bg); border: 1px solid var(--fg); }
#content { margin-left: 10px; }
#lang { position: static; }
#breadcrumb { left: auto; }
#breadcrumb { left: 48px; }
.js .foot { display: none; }
.js #menuopen { display: block; position: absolute; top: 3px; left: 6px; }
.nojs .logout, .nojs #menu { position: static; }
.rtl .pages { right: auto; }
.rtl #content { margin-right: 10px; }
.rtl #breadcrumb { right: auto; }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 B

View File

@@ -1,4 +1,4 @@
// Adminer specific functions
'use strict'; // Adminer specific functions
/** Load syntax highlighting
* @param string first three characters of database system version
@@ -6,7 +6,7 @@
*/
function syntaxHighlighting(version, vendor) {
if (window.jush) {
jush.create_links = ' target="_blank" rel="noreferrer noopener"';
jush.create_links = 'target="_blank" rel="noreferrer noopener"';
if (version) {
for (let key in jush.urls) {
let obj = jush.urls;
@@ -41,11 +41,16 @@ function syntaxHighlighting(version, vendor) {
jush.custom_links = jushLinks;
}
jush.highlight_tag('code', 0);
adminerHighlighter = els => jush.highlight_tag(els, 0);
for (const tag of qsa('textarea')) {
if (/(^|\s)jush-/.test(tag.className)) {
const pre = jush.textarea(tag);
if (pre) {
setupSubmitHighlightInput(pre);
tag.onchange = () => {
pre.textContent = tag.value;
pre.oninput();
};
}
}
}
@@ -249,11 +254,7 @@ function editFields() {
* @return boolean false to cancel action
*/
function editingClick(event) {
let el = event.target;
if (!isTag(el, 'input')) {
el = parentTag(el, 'label');
el = el && qs('input', el);
}
let el = parentTag(event.target, 'button');
if (el) {
const name = el.name;
if (/^add\[/.test(name)) {
@@ -264,18 +265,24 @@ function editingClick(event) {
editingMoveRow.call(el);
} else if (/^drop_col\[/.test(name)) {
editingRemoveRow.call(el, 'fields$1[field]');
} else {
if (name == 'auto_increment_col') {
const field = el.form['fields[' + el.value + '][field]'];
if (!field.value) {
field.value = 'id';
field.oninput();
}
}
return;
}
return false;
}
el = event.target;
if (!isTag(el, 'input')) {
el = parentTag(el, 'label');
el = el && qs('input', el);
}
if (el) {
const name = el.name;
if (name == 'auto_increment_col') {
const field = el.form['fields[' + el.value + '][field]'];
if (!field.value) {
field.value = 'id';
field.oninput();
}
}
}
}
/** Handle input on fields editing
@@ -334,11 +341,11 @@ function editingAddRow(focus) {
const x = match[0] + (match[2] ? added.substr(match[2].length) : added) + '1';
const row = parentTag(this, 'tr');
const row2 = cloneNode(row);
let tags = qsa('select', row);
let tags2 = qsa('select', row2);
let tags = qsa('select, input, button', row);
let tags2 = qsa('select, input, button', row2);
for (let i=0; i < tags.length; i++) {
tags2[i].name = tags[i].name.replace(/[0-9.]+/, x);
tags2[i].selectedIndex = tags[i].selectedIndex;
tags2[i].selectedIndex = (/\[(generated)/.test(tags[i].name) ? 0 : tags[i].selectedIndex);
}
tags = qsa('input', row);
tags2 = qsa('input', row2);
@@ -348,13 +355,11 @@ function editingAddRow(focus) {
tags2[i].value = x;
tags2[i].checked = false;
}
tags2[i].name = tags[i].name.replace(/([0-9.]+)/, x);
if (/\[(orig|field|comment|default)/.test(tags[i].name)) {
tags2[i].value = '';
}
if (/\[(generated)/.test(tags[i].name)) {
tags2[i].checked = false;
tags2[i].selectedIndex = 0;
}
}
tags[0].oninput = editingNameChange;
@@ -738,7 +743,7 @@ function schemaMouseup(event, db) {
that = undefined;
let s = '';
for (const key in tablePos) {
s += '_' + key + ':' + Math.round(tablePos[key][0] * 10000) / 10000 + 'x' + Math.round(tablePos[key][1] * 10000) / 10000;
s += '_' + key + ':' + Math.round(tablePos[key][0]) + 'x' + Math.round(tablePos[key][1]);
}
s = encodeURIComponent(s.substr(1));
const link = qs('#schema-link');

View File

@@ -1,3 +1,4 @@
'use strict';
/** Get first element by selector
* @param string
@@ -467,12 +468,10 @@ function bodyKeydown(event, button) {
}
if (isCtrl(event) && (event.keyCode == 13 || event.keyCode == 10) && isTag(target, 'select|textarea|input')) { // 13|10 - Enter
target.blur();
if (button) {
if (target.form[button]) {
target.form[button].click();
} else {
if (target.form.onsubmit) {
target.form.onsubmit();
}
target.form.dispatchEvent(new Event('submit', {bubbles: true}));
target.form.submit();
}
target.focus();
@@ -617,6 +616,7 @@ function ajaxSetHtml(url) {
}
let editChanged; // used by plugins
let adminerHighlighter = els => {}; // overwritten by syntax highlighters
/** Save form contents through AJAX
* @param HTMLFormElement
@@ -649,9 +649,7 @@ function ajaxForm(form, message, button) {
if (qs('.message', ajaxstatus)) { // success
editChanged = null;
}
if (window.jush) {
jush.highlight_tag(qsa('code', ajaxstatus), 0);
}
adminerHighlighter(qsa('code', ajaxstatus));
messagesPrint(ajaxstatus);
}, data, message);
}
@@ -764,10 +762,8 @@ function eventStop(event) {
* @param HTMLElement
*/
function setupSubmitHighlight(parent) {
for (const key in { input: 1, select: 1, textarea: 1 }) {
for (const input of qsa(key, parent)) {
setupSubmitHighlightInput(input);
}
for (const input of qsa('input, select, textarea', parent)) {
setupSubmitHighlightInput(input);
}
}
@@ -775,7 +771,7 @@ function setupSubmitHighlight(parent) {
* @param HTMLElement
*/
function setupSubmitHighlightInput(input) {
if (!/submit|image|file/.test(input.type)) {
if (!/submit|button|image|file/.test(input.type)) {
addEvent(input, 'focus', inputFocus);
addEvent(input, 'blur', inputBlur);
}
@@ -785,20 +781,14 @@ function setupSubmitHighlightInput(input) {
* @this HTMLInputElement
*/
function inputFocus() {
const submit = findDefaultSubmit(this);
if (submit) {
alterClass(submit, 'default', true);
}
alterClass(findDefaultSubmit(this), 'default', true);
}
/** Unhighlight default submit button
* @this HTMLInputElement
*/
function inputBlur() {
const submit = findDefaultSubmit(this);
if (submit) {
alterClass(submit, 'default');
}
alterClass(findDefaultSubmit(this), 'default');
}
/** Find submit button used by Enter
@@ -856,3 +846,9 @@ oninput = event => {
const maxLength = target.getAttribute('data-maxlength');
alterClass(target, 'maxlength', target.value && maxLength != null && target.value.length > maxLength); // maxLength could be 0
};
addEvent(document, 'click', event => {
if (!qs('#foot').contains(event.target)) {
alterClass(qs('#foot'), 'foot', true);
}
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 B

Some files were not shown because too many files have changed in this diff Show More