Compare commits

..

274 Commits

Author SHA1 Message Date
Jakub Vrana
39b339b1b5 Release 5.4.2 2026-02-08 16:44:05 +01:00
Jakub Vrana
de4104f70d Tests: Fix 2026-02-08 15:06:24 +01:00
Jakub Vrana
aeee1c1991 Fix typos 2026-02-08 13:34:51 +01:00
Jakub Vrana
21d3a31503 Avoid denial-of-service via version check (GHSA-q4f2-39gr-45jh, regression from 4.6.2) 2026-02-08 13:26:56 +01:00
Jakub Vrana
e4ac9d611e Don't quote comma in TSV export (fix #1238) 2026-02-08 09:01:52 +01:00
Jakub Vrana
bdc28bc959 non-PostgreSQL: display NOT NULL checks (fix #1237) 2026-02-08 08:44:40 +01:00
Jakub Vrana
83301e37da MariaDB: don't display checks with the same name from another table (bug #1135) 2026-02-08 08:38:50 +01:00
Jakub Vrana
1b2354cdae Plugins: ignore variables created in included files 2026-02-08 07:52:06 +01:00
Jakub Vrana
a5faea641a Plugin Designs: process before page (fix #1232) 2026-02-08 01:49:05 +01:00
Jakub Vrana
9f4a51819e Avoid deprecated each (fix #1218) 2026-02-08 01:22:38 +01:00
Anton Polonskiy
04473a21c8 ClickHouse: fix minor problems
* ClickHouse: Fix warning on empty tables

Warning:  reset() expects parameter 1 to be array, null given

* ClickHouse: Show correct error message

* ClickHouse: add CTE support + allow whitespace at the beginning of the query

* ClickHouse: set default format
2026-02-08 01:01:59 +01:00
Inoyatulloh
9d1451e659 PostgreSQL: fix namespace in inheritance links 2026-02-07 23:50:55 +01:00
M.K.
1879da5ad8 GitHub: allow for manual on-demand CI runs 2026-02-07 23:50:55 +01:00
Shun Zi
e471d8b0cf Fix escaping spaces in cookie value
`urlencode` encodes space into `+`, which breaks `adminer_permanent`, which is space-separated.
2026-02-07 23:50:54 +01:00
Frank Shrestha
5b9f0186a8 Compile: fix single language version on Windows 2026-02-07 21:49:09 +01:00
Ucky Deni Ulinnuha
da1ffdd34e Theme lavender-light: fix login list 2026-02-07 21:43:46 +01:00
Jakub Vrana
56c18bcca0 IGDB: hide Truncate and Drop 2026-02-07 21:35:09 +01:00
Jakub Vrana
948d57bb63 Shorten all but numeric and date types in select 2026-02-07 21:35:09 +01:00
Jakub Vrana
1a6735cac7 IGDB: display date in edit 2026-02-07 21:35:07 +01:00
Jakub Vrana
8e865cd650 IGDB: nicer printed select query 2026-02-07 21:32:50 +01:00
Jakub Vrana
864d831463 IGDB: print executed queries 2026-02-07 21:32:50 +01:00
Jakub Vrana
f61b085422 Plugins: Allow to be in any namespace 2026-02-07 21:32:50 +01:00
Jakub Vrana
0ddb097cb6 Hide Save button if there are no editable fields 2026-02-07 21:32:50 +01:00
Jakub Vrana
2674250862 Display uneditable fields in edit form 2026-02-07 21:32:45 +01:00
Jakub Vrana
6f6f576c41 Driver: customizable delimiter 2026-02-03 14:16:26 +01:00
Jakub Vrana
ecd9c74c99 Unify JSON pretty print 2026-02-03 14:16:26 +01:00
Jakub Vrana
2ea1f8a88c IGDB: webhooks 2026-02-03 14:16:24 +01:00
Jakub Vrana
691edde5fc Hide sort links on unsortable columns 2026-02-03 09:23:32 +01:00
Jakub Vrana
554e43aae4 Fix warning for unset enum value 2026-02-03 08:13:01 +01:00
Jakub Vrana
503d83599c IGDB: sort columns 2026-02-02 13:29:31 +01:00
Jakub Vrana
a7a704c818 New plugin: IGDB driver 2026-02-01 20:23:52 +01:00
Jakub Vrana
d356091c2f Hide unsupported alter table links 2026-02-01 12:09:57 +01:00
Jakub Vrana
a5ec07a77d Exact found rows except MySQL and PgSQL 2026-02-01 09:32:35 +01:00
Jakub Vrana
68e8b5bf69 Highlight odd rows in nested tables 2026-02-01 09:32:35 +01:00
Jakub Vrana
9c5215cf77 SQL: Unify value formatting with select 2026-02-01 09:32:33 +01:00
Jakub Vrana
e6fe48516e Improve print of nested tables 2026-02-01 09:31:37 +01:00
Jakub Vrana
58ff31a15d Save bytes 2026-02-01 09:18:22 +01:00
Jakub Vrana
ca7c4d90e1 Link //domain.tld values 2026-02-01 09:18:22 +01:00
Jakub Vrana
605ed2dcab Remove useless . "" 2026-02-01 09:18:22 +01:00
Jakub Vrana
76a8dbfb98 Support multiline generated values in alter table 2026-02-01 09:18:22 +01:00
Jakub Vrana
cca4d26784 PostgreSQL: Fix definition of complex generated columns 2026-02-01 09:18:22 +01:00
Jakub Vrana
80f9278d34 PostgreSQL: Allow creating NOT DEFERRABLE foreign keys 2026-02-01 09:18:22 +01:00
Jakub Vrana
c1838743ed PostgreSQL: Add schema to sequence and and view export 2026-02-01 09:18:22 +01:00
Jakub Vrana
eb614963f8 PostgreSQL: Remove duplicate DEFERRABLE in foreign key export 2026-02-01 09:18:22 +01:00
Jakub Vrana
1109ca6389 Add missing parentheses to CHECK export 2026-02-01 09:18:22 +01:00
Jakub Vrana
3856d0563e PostgreSQL: Offer foreign keys in create table 2026-02-01 09:18:22 +01:00
Jakub Vrana
a49fcf4799 Pretty print JSON in edit 2026-02-01 09:18:19 +01:00
Jakub Vrana
53d7e31bf6 PostgreSQL: Fix help link 2025-11-10 17:16:36 +01:00
Jakub Vrana
15f4183fa6 Add help for socket login (bug #1198) 2025-10-28 10:27:50 +01:00
Jakub Vrana
e6be47941b AdminerEnumOption: Fix after bug #1152 (fix #1191) 2025-10-27 15:34:38 +01:00
Marcus
240c8fc5e7 ClickHouse: Fix list of tables (bug #1176) 2025-10-27 15:24:03 +01:00
Jakub Vrana
57c5370c67 Prolong printed SQL query to 10000 characters (fix #1186) 2025-10-27 15:17:39 +01:00
Jakub Vrana
489f78c21c MySQL: Use information_schema to get routine definition (fix #1179) 2025-10-27 15:17:39 +01:00
Jakub Vrana
6c8de72707 Autocomplete: fix in empty textarea (fix #1173) 2025-10-27 15:17:39 +01:00
Marcus Nightingale
4d0e79234c ClickHouse: Fix offset (bug #1188)
Previously, the driver used `LIMIT <limit>, <offset>` syntax, causing incorrect
pagination behavior (page 2 repeating results from page 1, etc.). Updated the
`limit()` function to use ClickHouse-compatible `LIMIT <count> OFFSET <offset>`
syntax, ensuring correct row offsets across pages.
2025-10-27 15:17:28 +01:00
Jakub Vrana
c7ede7331e PostgreSQL: Mark unique partial indexes as unique (fix #1172) 2025-10-26 15:40:31 +01:00
Jakub Vrana
c5f3707bb9 AdminerRowNumbers: Fix before PHP 8.3.0 (fix #1167) 2025-10-26 15:27:48 +01:00
Jakub Vrana
1c008b7d71 Plugins: Methods showVariables() and showStatus() (bug #1157) 2025-10-26 15:15:10 +01:00
Jakub Vrana
c2be05f0e9 Develop 2025-09-26 17:48:03 +02:00
Jakub Vrana
eaad45a781 Release 5.4.1 2025-09-26 17:38:02 +02:00
Jakub Vrana
9a5b8f1f92 Do not quote 0 in CSV export 2025-09-16 10:18:07 +02:00
Jakub Vrana
1b43a6f034 Warn about exceeded upload_max_filesize in imports 2025-09-15 20:12:30 +02:00
Jakub Vrana
fdc2326376 Remove unused function 2025-09-15 19:51:56 +02:00
Jakub Vrana
08f93d6d09 Save bytes 2025-09-12 07:42:10 +02:00
Jakub Vrana
4fbe8ebf5a Update changelog 2025-09-11 15:10:07 +02:00
Jakub Vrana
86285dcf34 MySQL: Fix displaying routine definition (fix #1156, regression from 5.4.0) 2025-09-11 14:35:48 +02:00
Jakub Vrana
26c4057946 SQL command: Unlink NULL primary keys 2025-09-10 00:23:26 +02:00
Jakub Vrana
d15d0b2ef3 Add Composer script check 2025-09-10 00:17:44 +02:00
Jakub Vrána
bd1dffe086 CI: Run PHPStan 2025-09-09 08:28:20 +02:00
Jakub Vrana
4918ba407f Increase max. ?sql= length (fix #1154) 2025-09-08 21:30:44 +02:00
Jakub Vrana
46638ebd9a Develop 2025-09-08 11:43:22 +02:00
Jakub Vrana
6f339bac6c Release 5.4.0 2025-09-08 11:43:01 +02:00
Jakub Vrana
f32f84cf57 Use $this->conn 2025-09-08 11:01:25 +02:00
Jakub Vrana
ce063a64dd CockroachDB: Set default index algorithm 2025-09-08 10:59:19 +02:00
Jakub Vrana
1d5d441271 Fix tests 2025-09-08 10:52:58 +02:00
Jakub Vrana
fd28e64d8c MSSQL: Fix chechConstraints() 2025-09-08 10:21:58 +02:00
Jakub Vrana
3874148064 Save bytes 2025-09-08 08:44:44 +02:00
Jakub Vrana
0640326df4 SQLite PDO: Avoid double calling PRAGMA 2025-09-08 08:41:45 +02:00
Jakub Vrana
16f7080ff2 Define SERVER always as string 2025-09-08 08:37:49 +02:00
Jakub Vrana
516530485d Save bytes 2025-09-08 08:31:38 +02:00
Jakub Vrana
f921dafa61 Allow connecting to IPv6 (fix #1095) 2025-09-08 08:27:09 +02:00
Jakub Vrana
d1831641a9 MySQL: Fix saving empty enum (fix #1152) 2025-09-08 07:45:59 +02:00
Jakub Vrana
b1c825484d non-MySQL: Disable enum and set editing 2025-09-07 16:35:14 +02:00
Jakub Vrana
6c662e1f96 AdminerFileUpload: Replace invalid characters in table name 2025-09-07 16:18:57 +02:00
Jakub Vrana
7ef0949539 AdminerTimeout: Fix typo 2025-09-07 15:17:38 +02:00
Jakub Vrana
9fc63eb9d0 PostgreSQL: Export DROP and CREATE DATABASE (fix #1140) 2025-09-07 15:08:27 +02:00
Jakub Vrana
2041d5c6e3 Save bytes 2025-09-07 14:00:21 +02:00
Jakub Vrana
fc2ab7de16 PostgreSQL: Don't treat user types containing 'file' as blobs (fix #1118) 2025-09-07 13:52:04 +02:00
Jakub Vrana
2db30ba7e2 Bug report template: use comments 2025-09-07 12:11:07 +02:00
Lucas Sandery
8d6ed3fffa Design lucas-sandery: Theme update for v5 + dark mode 2025-09-07 12:00:42 +02:00
Lucas Sandery
9e0db9f1cd RTL precedence & JS-only fixes 2025-09-07 12:00:36 +02:00
Jakub Vrana
a979b4be22 Modernize JS: Use dataset 2025-09-07 11:45:42 +02:00
Jakub Vrana
26319460ef Fix code style 2025-09-07 11:21:05 +02:00
Jakub Vrana
b41699624b JS: modernize 2025-09-05 20:44:43 +02:00
Jakub Vrana
17a0e84718 Load more: run syntax highlighter 2025-09-05 20:42:12 +02:00
Jakub Vrana
5c5f7d17fe Copy hidden newlines to clipboard 2025-09-05 20:33:58 +02:00
Jakub Vrana
4ad7eb0b70 PostgreSQL: Fix calling functions returing table 2025-08-15 23:43:25 +02:00
Jakub Vrana
b83da41739 PostgreSQL: Fix calling functions with name-less parameters 2025-08-15 23:36:54 +02:00
Jakub Vrana
e551efb031 Executed SQL commands: Add button for copy to clipboard 2025-08-09 14:09:07 +02:00
Jakub Vrana
6d0351ec7c Autofocus added field in alter table 2025-08-09 07:28:04 +02:00
Jakub Vrana
dcf11d8fc9 PgSQL <11: Fix error in table list (fix #1128) 2025-08-04 07:46:14 +02:00
Jakub Vrana
0de6a057d3 PgSQL <10: Don't try partitions 2025-08-04 07:44:46 +02:00
Jakub Vrana
18b62aba38 Fix typo (fix #1137) 2025-08-02 08:24:00 +02:00
Jakub Vrana
10ff74552d PostgreSQL: Quote edit value with interval operator 2025-07-29 16:47:47 +02:00
Jakub Vrana
f5c7ab54f7 Update JUSH 2025-07-29 11:05:25 +02:00
Jakub Vrana
cc110c448c Plugins: PHP 5 compatibility (fix #1125) 2025-07-28 22:46:31 +02:00
Jakub Vrana
6f766f8c52 PostgreSQL: Limit dollar-quoted strings 2025-07-16 11:16:58 +02:00
Jakub Vrana
9a60d158f1 Increase routine textarea size 2025-07-07 15:22:46 +02:00
Jakub Vrana
6dec0d63b0 New plugin: Specify query timeout 2025-07-07 15:21:02 +02:00
Jakub Vrana
c9a52cd28c Link routines from syntax highlighting 2025-07-07 15:06:41 +02:00
Jakub Vrana
9424b7431e Elastic: Avoid extra newline 2025-07-07 15:06:41 +02:00
Jakub Vrana
35b2b969be Save vertical real estate 2025-06-28 18:04:30 +02:00
Jakub Vrana
165f1b241c AdminerBackwardKeys: Work with null ns in non-PgSQL 2025-06-28 11:31:05 +02:00
Jakub Vrana
4d75f822e9 PostgreSQL: Shorten values in hstore columns 2025-06-25 18:39:32 +02:00
Jakub Vrana
5e9c185596 Display data length and index length for materialized views 2025-06-25 18:38:34 +02:00
Jakub Vrana
cfd69dfd8c Trigger script=db sooner 2025-06-25 17:15:34 +02:00
Jakub Vrana
1fc5acb389 AdminerBackwardKeys: Skip relations without selected column 2025-06-25 14:57:35 +02:00
Jakub Vrana
8ca0e36970 AdminerBackwardKeys: Link only tables in the same schema 2025-06-25 14:52:12 +02:00
Jakub Vrana
9e8de24e3d Fix a typo 2025-06-25 14:39:55 +02:00
Jakub Vrana
4cb09852e3 Display @ after username without server in existing logins 2025-06-23 19:08:55 +02:00
Jakub Vrana
e115dccdae MariaDB: Parse COLLATE in routine definition (fix #1104) 2025-06-19 10:03:39 +02:00
Jakub Vrana
1ba410cad1 MySQL: Simplify routines() 2025-06-19 09:56:36 +02:00
Jakub Vrana
0764a20a19 New plugin: Display row numbers in select (fix #1106) 2025-06-19 09:35:01 +02:00
Jakub Vrana
b2c4574325 AdminerBackwardKeys: Strip table prefix 2025-06-19 09:25:03 +02:00
Jakub Vrana
466eceff40 Rename highlight plugins 2025-06-19 09:12:48 +02:00
Jakub Vrana
27c5f6d21b AdminerBackwardKeys: Support PostgreSQL 2025-06-18 18:21:38 +02:00
Jakub Vrana
2744538c8c Fix type error in multiple database export (fix #1109) 2025-06-18 10:40:06 +02:00
Matthaiks
41456c9eb7 Update Polish translation 2025-06-18 10:04:38 +02:00
Jakub Vrana
9745c12769 AdminerSqlGemini: Remove extra comments (fix #1075) 2025-06-18 09:36:17 +02:00
Jakub Vrana
f24f72ac51 Allow exporting SQL in SQL command (fix #1092) 2025-06-18 09:19:15 +02:00
Jakub Vrana
2b52a9b653 PostgreSQL: Allow comparing json columns (fix #1107) 2025-06-18 09:00:34 +02:00
Jakub Vrana
9ba4b86916 AdminerTablesFilter: Add title 2025-06-18 08:08:54 +02:00
Jakub Vrana
1cc3fdf915 Add section links in database overview 2025-06-11 19:33:07 +02:00
Jakub Vrana
b960c41d63 Elastic: Display indexes in alias (unused for now) 2025-06-11 15:16:19 +02:00
Jakub Vrana
3ec750ef7e Hide view links without view support 2025-06-11 15:16:17 +02:00
Jakub Vrana
737b631dda Elasticsearch: Support dropping aliases 2025-06-11 08:45:21 +02:00
Jakub Vrana
8fc450946c Driver plugins: Readme 2025-06-11 08:16:57 +02:00
Jakub Vrana
e282d6eb89 PostgreSQL: Display index expressions 2025-06-06 10:42:20 +02:00
Jakub Vrana
86ffeb2a1e PostgreSQL: Add SQL operator to select 2025-06-06 10:19:24 +02:00
Jakub Vrana
31530ba03e Do not order descending in GROUP BY select 2025-06-05 10:26:11 +02:00
Jakub Vrana
146d3539d8 Warn about exceeded max_file_uploads in import 2025-06-04 14:04:24 +02:00
Jakub Vrana
04068a631e Allow null value in where_link() 2025-06-03 16:17:55 +02:00
Jakub Vrana
4698686232 PostgreSQL: Hide only partitions, not all inherited tables from menu 2025-06-03 15:05:12 +02:00
Jakub Vrana
57b6afc8cb Fix heading of inherited tables 2025-06-03 14:40:08 +02:00
Jakub Vrana
e589c80f42 PostgreSQL: Show structure of inherited tables 2025-06-03 14:33:30 +02:00
Ludek Benedik
acfebf1788 Plugins pretty-json-column: Encode value to JSON without white spaces 2025-05-30 14:02:18 +02:00
Jakub Vrana
246c3c489b MySQL 5.0-: Do not load partitioning info in alter table (fix #1099) 2025-05-30 13:28:28 +02:00
yamamoto
92b95606c1 Updates Japanese translation (#1096) 2025-05-27 16:28:59 +02:00
Jakub Vrana
ac8318f387 PostgreSQL 11-: Avoid duplicate oid in table status (fix #1089) 2025-05-27 13:58:22 +02:00
Maxim Milovanov
d1c2679acd Add Win98-style design theme 2025-05-27 13:58:19 +02:00
Jakub Vrana
caf6e495dc Update externals 2025-05-27 10:37:12 +02:00
salacr
ec5ad85470 Make kill_process / process_list extendable 2025-05-07 08:29:43 +02:00
Jakub Vrana
307fabaf22 Allow specifying operator in search anywhere 2025-05-05 09:56:59 +02:00
schucan
1862b84612 Mute chmod warning 2025-05-05 08:05:36 +02:00
Jakub Vrana
e76be9f890 Develop 2025-05-05 08:04:55 +02:00
Jakub Vrana
1cfc8451ef Release 5.3.0 2025-05-04 18:30:08 +02:00
Jakub Vrana
62a9cf3e3f Editor: Fix bit edit 2025-05-04 15:20:11 +02:00
Jakub Vrana
e707c7a5f4 Editor: Fix bit and enum search (fix #1062) 2025-05-04 15:20:11 +02:00
Alex Yu
c92b127b56 Add lavender-light theme 2025-05-04 15:20:09 +02:00
Pexle Chris
cf52c4c0a8 AdminerDarkSwitcher: Handle first click
Fixed an issue where the Dark Mode Switcher icon did not respond on the first click if the Dark Mode cookie had not been set yet. Now, clicking the icon correctly sets the cookie and immediately toggles Dark Mode.
2025-05-04 13:10:38 +02:00
Jakub Vrana
5f7fb62803 PostgreSQL: Add NOT ILIKE operator (fix #1066) 2025-05-04 13:03:47 +02:00
Jakub Vrana
9b0acfa7c9 PostgreSQL: Align numbers in SQL command right (fix #1071) 2025-05-04 13:03:47 +02:00
Jakub Vrana
45d61803c4 Align money values right (bug #1071) 2025-05-04 13:03:47 +02:00
Jakub Vrana
48160f2cd7 Add border to column actions (fix #1072) 2025-05-04 13:03:46 +02:00
Jakub Vrana
61f2b370df AdminerSelectEmail: Use Adminer translation 2025-04-26 05:53:10 +02:00
Jakub Vrana
e13910b5c5 AdminerSelectEmail: Wrap long lines 2025-04-26 05:51:07 +02:00
Jakub Vrana
de86789bfc Indexes: Link algorithm doc 2025-04-26 05:43:25 +02:00
Jakub Vrana
ebcf4feeb2 Use variable 2025-04-26 05:31:48 +02:00
Jakub Vrana
d881f51deb MySQL: Fix connecting with port (fix #1057, regression from 5.1.1) 2025-04-24 22:31:03 +02:00
Matthaiks
b9c39e77fc Update Polish translation 2025-04-24 16:20:16 +02:00
salacr
c734deca84 PostgreSQL: Partial Indexes 2025-04-24 11:39:59 +02:00
Jakub Vrana
1466051402 Fix type error in Create function (fix #1053) 2025-04-21 08:36:32 +02:00
Joshi yogesh
36e55a3f55 Add Hindi translations for previously untranslated strings (#1052) 2025-04-19 20:05:11 +02:00
Joshi yogesh
155668906d Add Bengali (bn) translations for missing phrases (#1051) 2025-04-18 23:00:45 +02:00
Jakub Vrana
70ce02a798 AdminerTableIndexesStructure: Print algorithm 2025-04-18 15:00:08 +02:00
Jakub Vrana
de3220acc7 MySQL: Hide index algorithm if only one 2025-04-18 14:58:41 +02:00
Jakub Vrana
1030e84904 CSS: Remove icon padding on iOS 2025-04-18 14:37:16 +02:00
Jakub Vrana
f4ca974623 Add forgotten move_col 2025-04-18 06:53:03 +02:00
Jakub Vrana
af627e7116 AdminerForeignSystem: Link pg_catalog 2025-04-17 19:57:14 +02:00
Jakub Vrana
0c5bba47da non-MySQL: Fix computing unique array 2025-04-17 18:07:23 +02:00
Jakub Vrana
e340a2973e Select: Align numeric functions right 2025-04-17 18:00:49 +02:00
Jakub Vrana
be2afb49c5 PostgreSQL: Link COUNT(*) 2025-04-17 17:46:43 +02:00
Jakub Vrana
c8fa515ed5 CSS: Switch between independent adminer[-dark].css 2025-04-17 17:23:57 +02:00
Jakub Vrana
3dad86d279 Plugins: Allow setting dark mode in css() (bug #1049) 2025-04-17 16:58:58 +02:00
Jakub Vrana
d9c1ac00f3 Extract variable 2025-04-17 15:38:16 +02:00
Jakub Vrana
2a4d2cfb39 Fix typo in comment 2025-04-17 15:34:20 +02:00
Jakub Vrana
67a64c8d72 JUSH: PostgreSQL keywords, autocomplate table aliases 2025-04-17 14:26:55 +02:00
Jakub Vrana
d59d6c4075 MySQL: Support index algorithms with MEMORY and NDB engines 2025-04-17 10:54:04 +02:00
Jakub Vrana
dbec3a1b92 Pass $tableStatus to indexMethods, rename 2025-04-17 10:22:32 +02:00
Jakub Vrana
a93f0003ae MySQL: Remove negation from support() 2025-04-17 10:06:56 +02:00
Jakub Vrana
29a31c6b9c Lang: Use GLOB_BRACE 2025-04-17 08:49:53 +02:00
Joshi yogesh
a04823f4c4 Add Hindi (hi) language translation file for Adminer
This commit adds a new Hindi (hi) translation file for Adminer, based on the existing Bengali (bn) language file.

All interface strings have been translated from Bengali to Hindi to make Adminer more accessible to Hindi-speaking users. The structure of the file follows the format used in other language files within the repository.

The translations were carefully reviewed to ensure accuracy, readability, and consistency with the context of the Adminer application.

File added:
- lang/hi.inc.php

Looking forward to your feedback and happy to make any improvements as needed!
2025-04-17 08:43:05 +02:00
Jakub Vrana
82c544514d Move translation 2025-04-17 08:39:00 +02:00
salacr
7185d7854f Add PHP_CodeSniffer to pipeline (#1044) 2025-04-17 08:31:05 +02:00
Matthaiks
d845d4b358 Update Polish translation 2025-04-17 08:27:48 +02:00
salacr
92ce506243 PostgreSQL: Support index algorithms 2025-04-16 09:22:28 +02:00
Jakub Vrana
6d6998c3d3 PostgreSQL: Support calling functions returning table (fix #1040) 2025-04-15 23:22:36 +02:00
Jakub Vrana
45799f4605 AdminerSqlLog: Store schema 2025-04-15 22:47:48 +02:00
Jakub Vrana
b999f123c8 Swap ' and " 2025-04-15 21:59:41 +02:00
Jakub Vrana
7642b00877 Designs: Don't sniff protocol-relative URLs 2025-04-15 21:43:18 +02:00
Jakub Vrana
45be56e4e1 Add todo 2025-04-15 21:38:29 +02:00
Jakub Vrana
e1b92f73aa Issues: Link Docker image repo 2025-04-15 16:43:58 +02:00
Jakub Vrana
63850ebf19 CSS: Highlight anchor 2025-04-15 09:48:33 +02:00
Jakub Vrana
49fd96f8b9 PostgreSQL: Display partition info (bug #1031) 2025-04-15 08:58:24 +02:00
Matthaiks
fcae403f60 Update Polish translation 2025-04-15 08:40:48 +02:00
Jakub Vrana
7dc152b732 CockroachDB: Partitioning 2025-04-15 07:18:22 +02:00
Jakub Vrana
29f7b2df96 Tests PostgreSQL: Partitioning 2025-04-15 06:51:13 +02:00
Jakub Vrana
3466ab730b Partitioning: Compact array earlier 2025-04-15 06:46:23 +02:00
Jakub Vrana
9235cb8350 Doc: Update 2025-04-15 06:20:13 +02:00
Jakub Vrana
020285772b MySQL: Avoid warning on selecting tables with fulltext indexes (fix #1036) 2025-04-14 17:52:55 +02:00
Jakub Vrana
8238838285 Bug template: offer single language 2025-04-14 17:45:47 +02:00
Jakub Vrana
a165d4ed81 PostgreSQL: Export PARTITION BY (bug #1031) 2025-04-14 16:41:31 +02:00
Jakub Vrana
f5b42eae55 PostgreSQL: Allow renaming inherited tables 2025-04-14 16:03:44 +02:00
Jakub Vrana
460a24ea2d PostgreSQL: Creating partitioned tables (fix #1031) 2025-04-14 15:50:17 +02:00
Jakub Vrana
4646298015 Partitioning: Move to MySQL 2025-04-14 15:33:58 +02:00
Jakub Vrana
cde6b9008c Move partitioning functions 2025-04-14 13:54:20 +02:00
Jakub Vrana
8783f4d3ac PostgreSQL: Include inherited tables in table_status (bug #1031) 2025-04-14 13:13:08 +02:00
Jakub Vrana
3d2395fc59 Remove unnecessary aliases 2025-04-14 12:41:36 +02:00
Jakub Vrana
3a73815ba4 Do not attempt allFields without DB (fix #1033) 2025-04-14 09:21:51 +02:00
Jakub Vrana
d0a2de53ef Bug template: Ask for more info 2025-04-14 08:50:17 +02:00
Jakub Vrana
982233d7e5 Code style: Ignore long translations 2025-04-14 07:58:29 +02:00
salacr
cedfe97f40 Generic.Whitespace -> Generic.WhiteSpace 2025-04-14 07:56:56 +02:00
Jakub Vrana
9beb72edc2 PostgreSQL: Simplify OID 2025-04-13 22:42:05 +02:00
Jakub Vrana
9555c96d6a PostgreSQL: Link parent from inherited tables (bug #1031) 2025-04-13 16:59:16 +02:00
Jakub Vrana
a3d0bbba8f Compile: Fix type (fix #1027) 2025-04-13 16:36:15 +02:00
Jakub Vrana
a9bcde334f PostgreSQL: Move partitioned tables from table list to parent table (bug #1031) 2025-04-13 16:36:10 +02:00
Jakub Vrana
a735b795b2 PostgreSQL: Show partitioned tables as tables, not views (bug #1031) 2025-04-13 14:05:54 +02:00
Jakub Vrana
008cd33058 Designs: adminer.css with 'prefers-color-scheme: dark' don't disable dark mode (fix #1009) 2025-04-13 11:44:58 +02:00
Jakub Vrana
036ce4f1c5 Handle unloaded driver plugins 2025-04-13 08:31:56 +02:00
Jakub Vrana
5867b0724f Plugins: Method bodyClass() to add <body class> (fix #309) 2025-04-13 08:24:16 +02:00
Jakub Vrana
71f2578af6 Driver plugins: Show possible extensions 2025-04-12 09:01:30 +02:00
Jakub Vrana
c809216a56 Develop 2025-04-11 22:27:01 +02:00
Jakub Vrana
eb43ea3025 Release 5.2.1 2025-04-11 22:26:41 +02:00
Jakub Vrana
2ba833409a non-MySQL: Parse '--' without trailing space as comment in SQL command (fix #1025, regression from 5.2.0) 2025-04-11 22:15:14 +02:00
Jakub Vrana
5eaaa498d3 PostgreSQL PDO: Fix bytea without primary key (fix #1021) 2025-04-11 15:29:59 +02:00
Jakub Vrana
746c0a7b0b Update JUSH 2025-04-11 14:53:33 +02:00
Jakub Vrana
b30526213d Lang: Display line of the update 2025-04-10 19:04:06 +02:00
Jakub Vrana
6819815b88 Don't display Loaded plugins with only driver plugins 2025-04-10 18:56:38 +02:00
Jakub Vrana
a83626c8af Fix import without primary key (fix #1017, regression from 5.1.1) 2025-04-10 17:58:22 +02:00
Jakub Vrana
d4ddbc0639 Import: Add margin (fix #1012) 2025-04-10 17:32:03 +02:00
Jakub Vrana
a6cb91f0d2 AdminerLoginOtp: Translate 2025-04-09 08:05:06 +02:00
Jakub Vrana
78d3ce830d Do not highlight table 0 as active 2025-04-09 06:27:09 +02:00
Jakub Vrana
88099b7dd7 Code style: Fix 2025-04-08 21:26:08 +02:00
Jakub Vrana
8bce359fae Fix search anywhere (fix #1004, regression from 5.1.1) 2025-04-08 20:41:44 +02:00
Jakub Vrana
8ca7066625 AdminerMenuLinks: Translation in single language version (fix #1001) 2025-04-08 20:11:47 +02:00
Jakub Vrana
51bcc2a064 Develop 2025-04-08 18:29:13 +02:00
Jakub Vrana
588af652d4 Release 5.2.0 2025-04-08 18:28:50 +02:00
Jakub Vrana
b0c345f9be Update Poslish translations 2025-04-08 17:03:02 +02:00
Jakub Vrana
b2677187f1 Autocomplete: Populate only on pages where useful 2025-04-08 16:57:25 +02:00
Jakub Vrana
52ee085ca7 Update German translation (bug #1001) 2025-04-08 14:33:58 +02:00
Jakub Vrana
00459b302a Translations: Remove trailing fullstops 2025-04-08 13:49:21 +02:00
Jakub Vrana
17598c7ab3 Fix typo in translation 2025-04-08 13:47:36 +02:00
Jakub Vrana
b489cec651 Remove doubled spaces 2025-04-08 13:36:26 +02:00
Jakub Vrana
6da8bb670a Plugins: Compatibility with PHP 5 2025-04-08 13:14:32 +02:00
Takashi SHIRAI
e601a3d8ce Update Japanese translation
Signed-off-by: Takashi SHIRAI <shirai@nintendo.co.jp>
2025-04-08 13:10:05 +02:00
Jakub Vrana
efde7fcc6c Lang: Non-static $translations 2025-04-08 13:06:27 +02:00
Jakub Vrana
91b3526e8d Plugins: non-static $translations (fix #1000)
The main reason is that static properties are minified.
2025-04-08 12:57:03 +02:00
Jakub Vrana
a3ddd59015 Update externals 2025-04-08 12:54:06 +02:00
Jakub Vrana
7908d86c9f Doc: Plugin translations 2025-04-08 11:00:31 +02:00
Jakub Vrana
0cb41c63c7 Compile: Ignore $this->lang 2025-04-07 21:35:25 +02:00
Jakub Vrana
50de50571d AdminerSqlGemini: Version in User-Agent 2025-04-07 21:27:33 +02:00
Jakub Vrana
de38cb65b6 Designs: Fix logout background 2025-04-07 21:16:30 +02:00
Jakub Vrana
e6cc8bf91e AdminerConfig: Move link (fix #995) 2025-04-07 20:55:01 +02:00
Jakub Vrana
be6cf07d26 Foreign key: Avoid extra newline in error 2025-04-07 19:59:53 +02:00
Jakub Vrana
3f979793f7 Fix foreign key actions (regression from 5.1.1)
https://github.com/vrana/adminer/discussions/969#discussioncomment-12752117
2025-04-07 19:51:43 +02:00
Jakub Vrana
c8878d1652 CSS: Avoid footer shadow over text 2025-04-07 19:24:33 +02:00
Jakub Vrana
bf24198e68 Plugins: Link screenshot 2025-04-07 19:18:43 +02:00
Jakub Vrana
e33ead15e5 AdminerPlugin: Delete 2025-04-07 18:55:14 +02:00
Jakub Vrana
1087d55913 AdminerDotJs: Translate description 2025-04-07 18:53:38 +02:00
Jakub Vrana
38bdd0a961 lang.php: Add translation of plugin description 2025-04-07 18:26:31 +02:00
Jakub Vrana
3dd040abd1 Plugins: Translate descriptions (fix #994) 2025-04-07 18:26:28 +02:00
Jakub Vrana
3e455a4787 Plugins: Extend Adminer\Plugin 2025-04-07 17:02:16 +02:00
Jakub Vrana
95f14bca56 Plugins: Allow providing description 2025-04-07 15:54:31 +02:00
Jakub Vrana
121b77e866 Update German translation (bug #994) 2025-04-07 15:37:08 +02:00
139 changed files with 4336 additions and 1158 deletions

View File

@@ -7,9 +7,14 @@ assignees: ''
---
**Adminer version:** please use latest published or Git
**Driver:** e.g. MySQLi
**Database version:** e.g. 10.2.12-MariaDB
**Adminer version:** <!-- please use latest published or Git -->
**Compiled:** single file / single language / source codes / custom compilation
**Driver:** <!-- e.g. MySQLi -->
**Database version:** <!-- e.g. 10.2.12-MariaDB -->
**Plugins used:**
_Please provide reproducible steps including a SQL dump (with no personal information) if applicable.
Also please include a screenshot._
<!--
Please provide reproducible steps including a SQL dump (with no personal information) if applicable.
Also please include a screenshot.
Report issues with Adminer Docker image at https://github.com/TimWolla/docker-adminer._
-->

19
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,19 @@
name: CI
on:
pull_request:
branches: [ master ]
workflow_dispatch:
jobs:
build-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: php-actions/composer@v6
- uses: php-actions/phpcs@v1
with:
path: .
standard: phpcs.xml
- uses: php-actions/phpstan@v3

3
.gitmodules vendored
View File

@@ -1,6 +1,9 @@
[submodule "jush"]
path = externals/jush
url = https://github.com/vrana/jush
[submodule "JsShrink"]
path = externals/JsShrink
url = https://github.com/vrana/JsShrink
[submodule "PhpShrink"]
path = externals/PhpShrink
url = https://github.com/vrana/PhpShrink

View File

@@ -1,8 +1,102 @@
## Adminer dev
## Adminer 5.4.2 (released 2026-02-08)
- Avoid denial-of-service via version check (GHSA-q4f2-39gr-45jh, regression from 4.6.2)
- Pretty print JSON in edit
- Support multiline generated values in alter table
- Link //domain.tld values
- Improve print of nested tables
- Hide sort links on unsortable columns
- Display uneditable fields in edit form
- Shorten all but numeric and date types in select
- Fix escaping spaces in cookie value (bug #1208)
- Don't quote comma in TSV export (bug #1238)
- MariaDB: Don't display checks with the same name from another table (bug #1135)
- PostgreSQL: Offer foreign keys in create table
- PostgreSQL: Add missing parentheses to CHECK export
- PostgreSQL: Allow creating NOT DEFERRABLE foreign keys
- PostgreSQL: Remove duplicate DEFERRABLE in foreign key export
- PostgreSQL: Add schema to sequence and view export
- PostgreSQL: Fix definition of complex generated columns
- PostgreSQL: Mark unique partial indexes as unique (bug #1172)
- PostgreSQL: Fix namespace in inheritance links (bug #1221)
- non-PostgreSQL: Display NOT NULL checks (bug #1237)
- ClickHouse: Fix offset (bug #1188)
- ClickHouse: Fix list of tables (bug #1176)
- Plugins: Methods showVariables() and showStatus() (bug #1157)
- Plugins: Allow to be in any namespace
- New plugin: IGDB driver
## Adminer 5.4.1 (released 2025-09-26)
- SQL command: Unlink NULL primary keys
- Do not quote 0 in CSV export
- Warn about exceeded upload_max_filesize in imports
- Prolong queries saved from SQL command to URL (bug #1154)
- MySQL: Fix displaying routine definition (bug #1156, regression from 5.4.0)
## Adminer 5.4.0 (released 2025-09-08)
- Allow specifying operator in search anywhere
- Do not order descending in GROUP BY select
- Allow exporting SQL in SQL command (bug #1092)
- Add section links in database overview
- Warn about exceeded max_file_uploads in import
- Display @ after username without server in existing logins
- Display data length and index length for materialized views
- Link routines from syntax highlighting
- Autofocus added field in alter table
- Executed SQL commands: Add button for copy to clipboard
- Load more: run syntax highlighter
- Allow connecting to IPv6 (bug #1095)
- MySQL: Fix saving empty enum (bug #1152)
- MySQL 5.0-: Do not load partitioning info in alter table (bug #1099)
- MariaDB: Parse COLLATE in routine definition (bug #1104)
- PostgreSQL: Show structure of inherited tables
- PostgreSQL: Display index expressions
- PostgreSQL: Add SQL operator to select
- PostgreSQL: Hide only partitions, not all inherited tables from menu
- PostgreSQL: Allow comparing json columns (bug #1107)
- PostgreSQL: Shorten values in hstore columns
- PostgreSQL: Quote edit value with interval operator
- PostgreSQL: Fix calling functions with name-less parameters
- PostgreSQL: Fix calling functions returing table
- PostgreSQL: Don't treat user types containing 'file' as blobs (bug #1118)
- PostgreSQL: Export DROP and CREATE DATABASE (bug #1140)
- PostgreSQL 11-: Avoid duplicate oid in table status (bug #1089, regression from 5.3.0)
- Elasticsearch: Support dropping aliases
- Plugins: Methods afterConnect(), processList() and killProcess()
- New plugin: Display row numbers in select (bug #1106)
- New plugin: Specify query timeout
## Adminer 5.3.0 (released 2025-05-04)
- Align numeric functions right
- Autocomplete: Support table aliases
- Fix type error in Create function (bug #1053, regression from 5.1.1)
- Add border to column actions (bug #1072)
- Align money values right (bug #1071)
- MySQL: Avoid warning on selecting tables with fulltext indexes (bug #1036)
- MySQL, PostgreSQL: Support index algorithms (bug #1030)
- MySQL: Fix connecting to localhost:3306 (bug #1057, regression from 5.1.1)
- PostgreSQL, CockroachDB: Creating partitioned tables (bug #1031)
- PostgreSQL: Move partitioned tables from table list to parent table
- PostgreSQL: Support partial indices (bug #1048)
- PostgreSQL: Support calling functions returning table (bug #1040)
- PostgreSQL: Add NOT ILIKE operator (bug #1066)
- Editor: Fix bit and enum search (bug #1062)
- Designs: adminer.css with 'prefers-color-scheme: dark' doesn't disable dark mode
- Plugins: Method bodyClass() to add &lt;body class>
- Plugins: Allow setting dark mode in css()
- Hindi translation
## Adminer 5.2.1 (released 2025-04-11)
- Fix search anywhere (bug #1004, regression from 5.1.1)
- Fix import without primary key (bug #1017, regression from 5.1.1)
- PostgreSQL PDO: Fix bytea without primary key (bug #1021)
- non-MySQL: Parse '--' without trailing space as comment in SQL command (bug #1025, regression from 5.2.0)
## Adminer 5.2.0 (released 2025-04-08)
- Autocomplete SQL commands
- Do not edit NULL values by Modify (bug #967)
- PostgreSQL: Support COPY FROM stdin in SQL query (bug #942)
- Fix foreign key actions (regression from 5.1.1)
- MySQL: Display number of found rows in group queries (regression from 5.1.1)
- PostgreSQL: Support COPY FROM stdin in SQL query (bug #942)
- non-MySQL: Parse '--' without trailing space as comment in SQL command (bug SF-842)
- MS SQL: Limit one INSERT in export to 1000 rows (bug #983)
- CSS: Add logo
@@ -39,8 +133,8 @@
- CSS: Allow more custom styles with dark mode (bug #925)
- CSS: Increase maximum width of string edit (bug #930)
- CSS: Increase space after SQL result (bug #937)
- Plugins: autoload plugins from adminer-plugins/
- Plugins: configure plugins with adminer-plugins.php
- Plugins: Autoload plugins from adminer-plugins/
- Plugins: Configure plugins with adminer-plugins.php
- Plugins: Display loaded plugins in server overview
- New plugin: AI prompt in SQL command generating the queries with Google Gemini
- New plugin: Verify new versions from GitHub
@@ -89,8 +183,8 @@
- MariaDB: Fix creating and altering generated columns (bug #897)
- PostgreSQL: Fix "where" and "order" privileges (bug #902, regression from 5.0.2)
- SQLite: Fix creating table in compiled version (bug #901, regression from 5.0.0)
- Elastic: Do not pass null values on insert (PR #892)
- Elastic: Fix displaying sparse rows (PR #893)
- Elasticsearch: Do not pass null values on insert (PR #892)
- Elasticsearch: Fix displaying sparse rows (PR #893)
- Plugins: Add method dumpFooter()
## Adminer 5.0.2 (released 2025-03-10)
@@ -171,6 +265,7 @@
- SQLite: Fix expressions in default values (bug SF-860)
- MS SQL: Foreign keys in non-default schema (bug SF-833)
- Oracle: Include tables granted by other user
- Elasticsearch: Move to plugin
- MongoDB: Execute commands against the selected DB
## Adminer 4.15.0

View File

@@ -8,7 +8,7 @@ $routine = routine($_GET["call"], (isset($_GET["callf"]) ? "FUNCTION" : "PROCEDU
$in = array();
$out = array();
foreach ($routine["fields"] as $i => $field) {
if (substr($field["inout"], -3) == "OUT") {
if (substr($field["inout"], -3) == "OUT" && JUSH == 'sql') {
$out[$i] = "@" . idf_escape($field["field"]) . " AS " . idf_escape($field["field"]);
}
if (!$field["inout"] || substr($field["inout"], 0, 2) == "IN") {
@@ -29,10 +29,14 @@ if (!$error && $_POST) {
connection()->query("SET @" . idf_escape($field["field"]) . " = $val");
}
}
$call[] = (isset($out[$key]) ? "@" . idf_escape($field["field"]) : $val);
if (isset($out[$key])) {
$call[] = "@" . idf_escape($field["field"]);
} elseif (in_array($key, $in)) {
$call[] = $val;
}
}
$query = (isset($_GET["callf"]) ? "SELECT" : "CALL") . " " . table($PROCEDURE) . "(" . implode(", ", $call) . ")";
$query = (isset($_GET["callf"]) ? "SELECT " : "CALL ") . (idx($routine["returns"], "type") == "record" ? "* FROM " : "") . table($PROCEDURE) . "(" . implode(", ", $call) . ")";
$start = microtime(true);
$result = connection()->multi_query($query);
$affected = connection()->affected_rows; // getting warnings overwrites this

View File

@@ -2,10 +2,8 @@
namespace Adminer;
$TABLE = $_GET["create"];
$partition_by = array();
foreach (array('HASH', 'LINEAR HASH', 'KEY', 'LINEAR KEY', 'RANGE', 'LIST') as $key) {
$partition_by[$key] = $key;
}
$partition_by = driver()->partitionBy;
$partitions_info = ($partition_by ? driver()->partitionsInfo($TABLE) : array());
$referencable_primary = referencable_primary($TABLE);
$foreign_keys = array();
@@ -80,40 +78,26 @@ if ($_POST && !process_fields($row["fields"]) && !$error) {
}
}
$partitioning = "";
if (support("partitioning")) {
if (isset($partition_by[$row["partition_by"]])) {
$params = array();
foreach ($row as $key => $val) {
if (preg_match('~^partition~', $key)) {
$params[$key] = $val;
}
$partitioning = array();
if (in_array($row["partition_by"], $partition_by)) {
foreach ($row as $key => $val) {
if (preg_match('~^partition~', $key)) {
$partitioning[$key] = $val;
}
foreach ($params["partition_names"] as $key => $name) {
if ($name == "") {
unset($params["partition_names"][$key]);
unset($params["partition_values"][$key]);
}
}
if ($params != get_partitions_info($TABLE)) {
$partitions = array();
if ($params["partition_by"] == 'RANGE' || $params["partition_by"] == 'LIST') {
foreach ($params["partition_names"] as $key => $name) {
$value = $params["partition_values"][$key];
$partitions[] = "\n PARTITION " . idf_escape($name) . " VALUES " . ($params["partition_by"] == 'RANGE' ? "LESS THAN" : "IN") . ($value != "" ? " ($value)" : " MAXVALUE"); //! SQL injection
}
}
// $params["partition"] can be expression, not only column
$partitioning .= "\nPARTITION BY $params[partition_by]($params[partition])";
if ($partitions) {
$partitioning .= " (" . implode(",", $partitions) . "\n)";
} elseif ($params["partitions"]) {
$partitioning .= " PARTITIONS " . (+$params["partitions"]);
}
}
} elseif (preg_match("~partitioned~", $table_status["Create_options"])) {
$partitioning .= "\nREMOVE PARTITIONING";
}
foreach ($partitioning["partition_names"] as $key => $name) {
if ($name == "") {
unset($partitioning["partition_names"][$key]);
unset($partitioning["partition_values"][$key]);
}
}
$partitioning["partition_names"] = array_values($partitioning["partition_names"]);
$partitioning["partition_values"] = array_values($partitioning["partition_values"]);
if ($partitioning == $partitions_info) {
$partitioning = array();
}
} elseif (preg_match("~partitioned~", $table_status["Create_options"])) {
$partitioning = null;
}
$message = lang('Table has been altered.');
@@ -159,8 +143,8 @@ if (!$_POST) {
$row["fields"][] = $field;
}
if (support("partitioning")) {
$row += get_partitions_info($TABLE);
if ($partition_by) {
$row += $partitions_info;
$row["partition_names"][] = "";
$row["partition_values"][] = "";
}
@@ -221,10 +205,10 @@ if (support("columns")) {
<input type="submit" name="drop" value="<?php echo lang('Drop'); ?>"><?php echo confirm(lang('Drop %s?', $TABLE)); ?>
<?php } ?>
<?php
if (support("partitioning")) {
if ($partition_by && (JUSH == 'sql' || $TABLE == "")) {
$partition_table = preg_match('~RANGE|LIST~', $row["partition_by"]);
print_fieldset("partition", lang('Partition by'), $row["partition_by"]);
echo "<p>" . html_select("partition_by", array("" => "") + $partition_by, $row["partition_by"]) . on_help("event.target.value.replace(/./, 'PARTITION BY \$&')", 1) . script("qsl('select').onchange = partitionByChange;");
echo "<p>" . html_select("partition_by", array_merge(array(""), $partition_by), $row["partition_by"]) . on_help("event.target.value.replace(/./, 'PARTITION BY \$&')", 1) . script("qsl('select').onchange = partitionByChange;");
echo "(<input name='partition' value='" . h($row["partition"]) . "'>)\n";
echo lang('Partitions') . ": <input type='number' name='partitions' class='size" . ($partition_table || !$row["partition_by"] ? " hidden" : "") . "' value='" . h($row["partitions"]) . "'>\n";
echo "<table id='partition-table'" . ($partition_table ? "" : " class='hidden'") . ">\n";

View File

@@ -64,12 +64,13 @@ if (adminer()->homepage()) {
echo "<form action='' method='post'>\n";
if (support("table")) {
echo "<fieldset><legend>" . lang('Search data in tables') . " <span id='selected2'></span></legend><div>";
echo "<input type='search' name='query' value='" . h($_POST["query"]) . "'>";
echo html_select("op", adminer()->operators(), idx($_POST, "op", JUSH == "elastic" ? "should" : "LIKE %%"));
echo " <input type='search' name='query' value='" . h($_POST["query"]) . "'>";
echo script("qsl('input').onkeydown = partialArg(bodyKeydown, 'search');", "");
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"] = $_POST["op"];
search_tables();
}
}
@@ -79,14 +80,27 @@ if (adminer()->homepage()) {
echo '<thead><tr class="wrap">';
echo '<td><input id="check-all" type="checkbox" class="jsonly">' . script("qs('#check-all').onclick = partial(formCheck, /^(tables|views)\[/);", "");
echo '<th>' . lang('Table');
echo '<td>' . lang('Engine') . doc_link(array('sql' => 'storage-engines.html'));
echo '<td>' . lang('Collation') . doc_link(array('sql' => 'charset-charsets.html', 'mariadb' => 'supported-character-sets-and-collations/'));
echo '<td>' . lang('Data Length') . doc_link(array('sql' => 'show-table-status.html', 'pgsql' => 'functions-admin.html#FUNCTIONS-ADMIN-DBOBJECT', 'oracle' => 'REFRN20286'));
echo '<td>' . lang('Index Length') . doc_link(array('sql' => 'show-table-status.html', 'pgsql' => 'functions-admin.html#FUNCTIONS-ADMIN-DBOBJECT'));
echo '<td>' . lang('Data Free') . doc_link(array('sql' => 'show-table-status.html'));
echo '<td>' . lang('Auto Increment') . doc_link(array('sql' => 'example-auto-increment.html', 'mariadb' => 'auto_increment/'));
echo '<td>' . lang('Rows') . doc_link(array('sql' => 'show-table-status.html', 'pgsql' => 'catalog-pg-class.html#CATALOG-PG-CLASS', 'oracle' => 'REFRN20286'));
echo (support("comment") ? '<td>' . lang('Comment') . doc_link(array('sql' => 'show-table-status.html', 'pgsql' => 'functions-info.html#FUNCTIONS-INFO-COMMENT-TABLE')) : '');
$columns = array("Engine" => array(lang('Engine') . doc_link(array('sql' => 'storage-engines.html'))));
if (collations()) {
$columns["Collation"] = array(lang('Collation') . doc_link(array('sql' => 'charset-charsets.html', 'mariadb' => 'supported-character-sets-and-collations/')));
}
if (function_exists('Adminer\alter_table')) {
$columns["Data_length"] = array(lang('Data Length') . doc_link(array('sql' => 'show-table-status.html', 'pgsql' => 'functions-admin.html#FUNCTIONS-ADMIN-DBOBJECT', 'oracle' => 'REFRN20286')), "create", lang('Alter table'));
}
if (support('indexes')) {
$columns["Index_length"] = array(lang('Index Length') . doc_link(array('sql' => 'show-table-status.html', 'pgsql' => 'functions-admin.html#FUNCTIONS-ADMIN-DBOBJECT')), "indexes", lang('Alter indexes'));
}
$columns["Data_free"] = array(lang('Data Free') . doc_link(array('sql' => 'show-table-status.html')), "edit", lang('New item'));
if (function_exists('Adminer\alter_table')) {
$columns["Auto_increment"] = array(lang('Auto Increment') . doc_link(array('sql' => 'example-auto-increment.html', 'mariadb' => 'auto_increment/')), "auto_increment=1&create", lang('Alter table'));
}
$columns["Rows"] = array(lang('Rows') . doc_link(array('sql' => 'show-table-status.html', 'pgsql' => 'catalog-pg-class.html#CATALOG-PG-CLASS', 'oracle' => 'REFRN20286')), "select", lang('Select data'));
if (support("comment")) {
$columns["Comment"] = array(lang('Comment') . doc_link(array('sql' => 'show-table-status.html', 'pgsql' => 'functions-info.html#FUNCTIONS-INFO-COMMENT-TABLE')));
}
foreach ($columns as $column) {
echo "<td>$column[0]";
}
echo "</thead>\n";
$tables = 0;
@@ -95,30 +109,20 @@ if (adminer()->homepage()) {
$id = h("Table-" . $name);
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>';
if ($view && !preg_match('~materialized~i', $type)) {
$title = lang('View');
echo '<td colspan="6">' . (support("view") ? "<a href='" . h(ME) . "view=" . urlencode($name) . "' title='" . lang('Alter view') . "'>$title</a>" : $title);
echo '<td align="right"><a href="' . h(ME) . "select=" . urlencode($name) . '" title="' . lang('Select data') . '">?</a>';
} else {
foreach (
array(
"Engine" => array(),
"Collation" => array(),
"Data_length" => array("create", lang('Alter table')),
"Index_length" => array("indexes", lang('Alter indexes')),
"Data_free" => array("edit", lang('New item')),
"Auto_increment" => array("auto_increment=1&create", lang('Alter table')),
"Rows" => array("select", lang('Select data')),
) as $key => $link
) {
foreach ($columns as $key => $column) {
$id = " id='$key-" . h($name) . "'";
echo ($link ? "<td align='right'>" . (support("table") || $key == "Rows" || (support("indexes") && $key != "Data_length")
? "<a href='" . h(ME . "$link[0]=") . urlencode($name) . "'$id title='$link[1]'>?</a>"
: "<span$id>?</span>"
) : "<td id='$key-" . h($name) . "'>");
echo ($column[1]
? "<td align='right'><a href='" . h(ME . "$column[1]=") . urlencode($name) . "'$id title='$column[2]'>?</a>"
: "<td id='$key-" . h($name) . "'>"
);
}
$tables++;
}
echo (support("comment") ? "<td id='Comment-" . h($name) . "'>" : "");
echo "\n";
}
@@ -126,46 +130,51 @@ if (adminer()->homepage()) {
echo "<td>" . h(JUSH == "sql" ? get_val("SELECT @@default_storage_engine") : "");
echo "<td>" . h(db_collation(DB, collations()));
foreach (array("Data_length", "Index_length", "Data_free") as $key) {
echo "<td align='right' id='sum-$key'>";
echo ($columns[$key] ? "<td align='right' id='sum-$key'>" : "");
}
echo "\n";
echo "</table>\n";
echo script("ajaxSetHtml('" . js_escape(ME) . "script=db');");
echo "</div>\n";
if (!information_schema(DB)) {
echo "<div class='footer'><div>\n";
$vacuum = "<input type='submit' value='" . lang('Vacuum') . "'> " . on_help("'VACUUM'");
$optimize = "<input type='submit' name='optimize' value='" . lang('Optimize') . "'> " . on_help(JUSH == "sql" ? "'OPTIMIZE TABLE'" : "'VACUUM OPTIMIZE'");
echo "<fieldset><legend>" . lang('Selected') . " <span id='selected'></span></legend><div>"
. (JUSH == "sqlite" ? $vacuum . "<input type='submit' name='check' value='" . lang('Check') . "'> " . on_help("'PRAGMA integrity_check'")
$print = (JUSH == "sqlite" ? $vacuum . "<input type='submit' name='check' value='" . lang('Check') . "'> " . on_help("'PRAGMA integrity_check'")
: (JUSH == "pgsql" ? $vacuum . $optimize
: (JUSH == "sql" ? "<input type='submit' value='" . lang('Analyze') . "'> " . on_help("'ANALYZE TABLE'")
. $optimize
. "<input type='submit' name='check' value='" . lang('Check') . "'> " . on_help("'CHECK TABLE'")
. "<input type='submit' name='repair' value='" . lang('Repair') . "'> " . on_help("'REPAIR TABLE'")
: "")))
. "<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";
. (function_exists('Adminer\truncate_tables') ? "<input type='submit' name='truncate' value='" . lang('Truncate') . "'> " . on_help(JUSH == "sqlite" ? "'DELETE'" : "'TRUNCATE" . (JUSH == "pgsql" ? "'" : " TABLE'")) . confirm() : "")
. (function_exists('Adminer\drop_tables') ? "<input type='submit' name='drop' value='" . lang('Drop') . "'>" . on_help("'DROP TABLE'") . confirm() : "");
echo ($print ? "<div class='footer'><div>\n<fieldset><legend>" . lang('Selected') . " <span id='selected'></span></legend><div>$print\n</div></fieldset>\n" : "");
$databases = (support("scheme") ? adminer()->schemas() : adminer()->databases());
$script = "";
if (count($databases) != 1 && JUSH != "sqlite") {
echo "<fieldset><legend>" . lang('Move to other database') . " <span id='selected3'></span></legend><div>";
$db = (isset($_POST["target"]) ? $_POST["target"] : (support("scheme") ? $_GET["ns"] : DB));
echo "<p><label>" . lang('Move to other database') . ": ";
echo ($databases ? html_select("target", $databases, $db) : '<input name="target" value="' . h($db) . '" autocapitalize="off">');
echo "</label> <input type='submit' name='move' value='" . lang('Move') . "'>";
echo (support("copy") ? " <input type='submit' name='copy' value='" . lang('Copy') . "'> " . checkbox("overwrite", 1, $_POST["overwrite"], lang('overwrite')) : "");
echo "\n";
echo "</div></fieldset>\n";
$script = " selectCount('selected3', formChecked(this, /^(tables|views)\[/));";
}
echo "<input type='hidden' name='all' value=''>"; // used by trCheck()
echo script("qsl('input').onclick = function () { selectCount('selected', formChecked(this, /^(tables|views)\[/));" . (support("table") ? " selectCount('selected2', formChecked(this, /^tables\[/) || $tables);" : "") . " }");
echo script("qsl('input').onclick = function () { selectCount('selected', formChecked(this, /^(tables|views)\[/));"
. (support("table") ? " selectCount('selected2', formChecked(this, /^tables\[/) || $tables);" : "")
. "$script }")
;
echo input_token();
echo "</div></fieldset>\n";
echo "</div></div>\n";
}
echo "</form>\n";
echo script("tableCheck();");
}
echo "<p class='links'><a href='" . h(ME) . "create='>" . lang('Create table') . "</a>\n";
echo (function_exists('Adminer\alter_table') ? "<p class='links'><a href='" . h(ME) . "create='>" . lang('Create table') . "</a>\n" : '');
echo (support("view") ? "<a href='" . h(ME) . "view='>" . lang('Create view') . "</a>\n" : "");
if (support("routine")) {
@@ -239,9 +248,5 @@ if (adminer()->homepage()) {
}
echo '<p class="links"><a href="' . h(ME) . 'event=">' . lang('Create event') . "</a>\n";
}
if ($tables_list) {
echo script("ajaxSetHtml('" . js_escape(ME) . "script=db');");
}
}
}

View File

@@ -11,6 +11,7 @@ add_driver("mssql", "MS SQL");
if (isset($_GET["mssql"])) {
define('Adminer\DRIVER', "mssql");
if (extension_loaded("sqlsrv") && $_GET["ext"] != "pdo") {
class Db extends SqlDb {
public $extension = "sqlsrv";
@@ -25,7 +26,7 @@ if (isset($_GET["mssql"])) {
$this->error = rtrim($this->error);
}
function attach(?string $server, string $username, string $password): string {
function attach(string $server, string $username, string $password): string {
$connection_info = array("UID" => $username, "PWD" => $password, "CharacterSet" => "UTF-8");
$ssl = adminer()->connectSsl();
if (isset($ssl["Encrypt"])) {
@@ -38,7 +39,8 @@ if (isset($_GET["mssql"])) {
if ($db != "") {
$connection_info["Database"] = $db;
}
$this->link = @sqlsrv_connect(preg_replace('~:~', ',', $server), $connection_info);
list($host, $port) = host_port($server);
$this->link = @sqlsrv_connect($host . ($port ? ",$port" : ""), $connection_info);
if ($this->link) {
$info = sqlsrv_server_info($this->link);
$this->server_info = $info['SQLServerVersion'];
@@ -180,8 +182,9 @@ if (isset($_GET["mssql"])) {
class Db extends MssqlDb {
public $extension = "PDO_SQLSRV";
function attach(?string $server, string $username, string $password): string {
return $this->dsn("sqlsrv:Server=" . str_replace(":", ",", $server), $username, $password);
function attach(string $server, string $username, string $password): string {
list($host, $port) = host_port($server);
return $this->dsn("sqlsrv:Server=$host" . ($port ? ",$port" : ""), $username, $password);
}
}
@@ -189,8 +192,9 @@ if (isset($_GET["mssql"])) {
class Db extends MssqlDb {
public $extension = "PDO_DBLIB";
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);
function attach(string $server, string $username, string $password): string {
list($host, $port) = host_port($server);
return $this->dsn("dblib:charset=utf8;host=$host" . ($port ? (is_numeric($port) ? ";port=" : ";unix_socket=") . $port : ""), $username, $password);
}
}
}
@@ -213,7 +217,7 @@ if (isset($_GET["mssql"])) {
public $generated = array("PERSISTED", "VIRTUAL");
public $onActions = "NO ACTION|CASCADE|SET NULL|SET DEFAULT";
static function connect(?string $server, string $username, string $password) {
static function connect(string $server, string $username, string $password) {
if ($server == "") {
$server = "localhost:1433";
}
@@ -677,7 +681,7 @@ WHERE sys1.xtype = 'TR' AND sys2.name = " . q($table)) as $row
return "TRUNCATE TABLE " . table($table);
}
function use_sql($database) {
function use_sql($database, $style = "") {
return "USE " . idf_escape($database);
}

View File

@@ -5,6 +5,7 @@ SqlDriver::$drivers = array("server" => "MySQL / MariaDB") + SqlDriver::$drivers
if (!defined('Adminer\DRIVER')) {
define('Adminer\DRIVER', "server"); // server - backwards compatibility
// MySQLi supports everything, MySQL doesn't support multiple result sets, PDO_MySQL doesn't support orgtable
if (extension_loaded("mysqli") && $_GET["ext"] != "pdo") {
class Db extends \MySQLi {
@@ -15,9 +16,9 @@ if (!defined('Adminer\DRIVER')) {
parent::init();
}
function attach(?string $server, string $username, string $password): string {
function attach(string $server, string $username, string $password): string {
mysqli_report(MYSQLI_REPORT_OFF); // stays between requests, not required since PHP 5.3.4
list($host, $port) = explode(":", $server, 2); // part after : is used for port or socket
list($host, $port) = host_port($server);
$ssl = adminer()->connectSsl();
if ($ssl) {
$this->ssl_set($ssl['key'], $ssl['cert'], $ssl['ca'], '', '');
@@ -28,10 +29,10 @@ if (!defined('Adminer\DRIVER')) {
($server . $username . $password != "" ? $password : ini_get("mysqli.default_pw")),
null,
(is_numeric($port) ? intval($port) : ini_get("mysqli.default_port")),
(is_numeric($port) ? $port : null),
(is_numeric($port) ? null : $port),
($ssl ? ($ssl['verify'] !== false ? 2048 : 64) : 0) // 2048 - MYSQLI_CLIENT_SSL, 64 - MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT (not available before PHP 5.6.16)
);
$this->options(MYSQLI_OPT_LOCAL_INFILE, false);
$this->options(MYSQLI_OPT_LOCAL_INFILE, 0);
return ($return ? '' : $this->error);
}
@@ -57,14 +58,14 @@ if (!defined('Adminer\DRIVER')) {
class Db extends SqlDb {
/** @var resource */ private $link;
function attach(?string $server, string $username, string $password): string {
function attach(string $server, string $username, string $password): string {
if (ini_bool("mysql.allow_local_infile")) {
return lang('Disable %s or enable %s or %s extensions.', "'mysql.allow_local_infile'", "MySQLi", "PDO_MySQL");
}
$this->link = @mysql_connect(
($server != "" ? $server : ini_get("mysql.default_host")),
("$server$username" != "" ? $username : ini_get("mysql.default_user")),
("$server$username$password" != "" ? $password : ini_get("mysql.default_password")),
($server . $username != "" ? $username : ini_get("mysql.default_user")),
($server . $username . $password != "" ? $password : ini_get("mysql.default_password")),
true,
131072 // CLIENT_MULTI_RESULTS for CALL
);
@@ -157,7 +158,7 @@ if (!defined('Adminer\DRIVER')) {
class Db extends PdoDb {
public $extension = "PDO_MySQL";
function attach(?string $server, string $username, string $password): string {
function attach(string $server, string $username, string $password): string {
$options = array(\PDO::MYSQL_ATTR_LOCAL_INFILE => false);
$ssl = adminer()->connectSsl();
if ($ssl) {
@@ -174,8 +175,9 @@ if (!defined('Adminer\DRIVER')) {
$options[\PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT] = $ssl['verify'];
}
}
list($host, $port) = host_port($server);
return $this->dsn(
"mysql:charset=utf8;host=" . str_replace(":", ";unix_socket=", preg_replace('~:(\d)~', ';port=\1', $server)),
"mysql:charset=utf8;host=$host" . ($port ? (is_numeric($port) ? ";port=" : ";unix_socket=") . $port : ""),
$username,
$password,
$options
@@ -210,7 +212,7 @@ if (!defined('Adminer\DRIVER')) {
public $functions = array("char_length", "date", "from_unixtime", "lower", "round", "floor", "ceil", "sec_to_time", "time_to_sec", "upper");
public $grouping = array("avg", "count", "count distinct", "group_concat", "max", "min", "sum");
static function connect(?string $server, string $username, string $password) {
static function connect(string $server, string $username, string $password) {
$connection = parent::connect($server, $username, $password);
if (is_string($connection)) {
if (function_exists('iconv') && !is_utf8($connection) && strlen($s = iconv("windows-1250", "utf-8", $connection)) > strlen($connection)) { // windows-1250 - most common Windows encoding
@@ -257,6 +259,9 @@ if (!defined('Adminer\DRIVER')) {
$this->types[lang('Numbers')]["vector"] = 16383;
$this->insertFunctions['vector'] = 'string_to_vector';
}
if (min_version(5.1, '', $connection)) {
$this->partitionBy = array("HASH", "LINEAR HASH", "KEY", "LINEAR KEY", "RANGE", "LIST");
}
if (min_version(5.7, 10.2, $connection)) {
$this->generated = array("STORED", "VIRTUAL");
}
@@ -334,6 +339,17 @@ if (!defined('Adminer\DRIVER')) {
}
}
function partitionsInfo(string $table): array {
$from = "FROM information_schema.PARTITIONS WHERE TABLE_SCHEMA = " . q(DB) . " AND TABLE_NAME = " . q($table);
$result = $this->conn->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");
$return["partition_names"] = array_keys($partitions);
$return["partition_values"] = array_values($partitions);
return $return;
}
function hasCStyleEscapes(): bool {
static $c_style;
if ($c_style === null) {
@@ -352,6 +368,10 @@ if (!defined('Adminer\DRIVER')) {
}
return $return;
}
function indexAlgorithms(array $tableStatus): array {
return (preg_match('~^(MEMORY|NDB)$~', $tableStatus["Engine"]) ? array("HASH", "BTREE") : array());
}
}
@@ -438,7 +458,7 @@ if (!defined('Adminer\DRIVER')) {
/** Get table status
* @param bool $fast return only "Name", "Engine" and "Comment" fields
* @return TableStatus[]
* @return array<string, TableStatus>
*/
function table_status(string $name = "", bool $fast = false): array {
$return = array();
@@ -543,6 +563,7 @@ if (!defined('Adminer\DRIVER')) {
$return[$name]["columns"][] = $row["Column_name"];
$return[$name]["lengths"][] = ($row["Index_type"] == "SPATIAL" ? null : $row["Sub_part"]);
$return[$name]["descs"][] = null;
$return[$name]["algorithm"] = $row["Index_type"];
}
return $return;
}
@@ -556,7 +577,7 @@ if (!defined('Adminer\DRIVER')) {
$create_table = get_val("SHOW CREATE TABLE " . table($table), 1);
if ($create_table) {
preg_match_all(
"~CONSTRAINT ($pattern) FOREIGN KEY ?\\(((?:$pattern,? ?)+)\\) REFERENCES ($pattern)(?:\\.($pattern))? \\(((?:$pattern,? ?)+)\\)(?: ON DELETE (driver()->onActions))?(?: ON UPDATE (driver()->onActions))?~",
"~CONSTRAINT ($pattern) FOREIGN KEY ?\\(((?:$pattern,? ?)+)\\) REFERENCES ($pattern)(?:\\.($pattern))? \\(((?:$pattern,? ?)+)\\)(?: ON DELETE (" . driver()->onActions . "))?(?: ON UPDATE (" . driver()->onActions . "))?~",
$create_table,
$matches,
PREG_SET_ORDER
@@ -675,10 +696,11 @@ if (!defined('Adminer\DRIVER')) {
* @param string $name new name
* @param list<array{string, list<string>, string}> $fields of [$orig, $process_field, $after]
* @param string[] $foreign
* @param numeric-string $auto_increment
* @param numeric-string|'' $auto_increment
* @param ?Partitions $partitioning null means remove partitioning
* @return Result|bool
*/
function alter_table(string $table, string $name, array $fields, array $foreign, ?string $comment, string $engine, string $collation, string $auto_increment, string $partitioning) {
function alter_table(string $table, string $name, array $fields, array $foreign, ?string $comment, string $engine, string $collation, string $auto_increment, ?array $partitioning) {
$alter = array();
foreach ($fields as $field) {
if ($field[1]) {
@@ -699,8 +721,28 @@ if (!defined('Adminer\DRIVER')) {
. ($collation ? " COLLATE " . q($collation) : "")
. ($auto_increment != "" ? " AUTO_INCREMENT=$auto_increment" : "")
;
if ($partitioning) {
$partitions = array();
if ($partitioning["partition_by"] == 'RANGE' || $partitioning["partition_by"] == 'LIST') {
foreach ($partitioning["partition_names"] as $key => $val) {
$value = $partitioning["partition_values"][$key];
$partitions[] = "\n PARTITION " . idf_escape($val) . " VALUES " . ($partitioning["partition_by"] == 'RANGE' ? "LESS THAN" : "IN") . ($value != "" ? " ($value)" : " MAXVALUE"); //! SQL injection
}
}
// $partitioning["partition"] can be expression, not only column
$status .= "\nPARTITION BY $partitioning[partition_by]($partitioning[partition])";
if ($partitions) {
$status .= " (" . implode(",", $partitions) . "\n)";
} elseif ($partitioning["partitions"]) {
$status .= " PARTITIONS " . (+$partitioning["partitions"]);
}
} elseif ($partitioning === null) {
$status .= "\nREMOVE PARTITIONING";
}
if ($table == "") {
return queries("CREATE TABLE " . table($name) . " (\n" . implode(",\n", $alter) . "\n)$status$partitioning");
return queries("CREATE TABLE " . table($name) . " (\n" . implode(",\n", $alter) . "\n)$status");
}
if ($table != $name) {
$alter[] = "RENAME TO " . table($name);
@@ -708,12 +750,12 @@ if (!defined('Adminer\DRIVER')) {
if ($status) {
$alter[] = ltrim($status);
}
return ($alter || $partitioning ? queries("ALTER TABLE " . table($table) . "\n" . implode(",\n", $alter) . $partitioning) : true);
return ($alter ? queries("ALTER TABLE " . table($table) . "\n" . implode(",\n", $alter)) : true);
}
/** Run commands to alter indexes
* @param string $table escaped table name
* @param list<array{string, string, 'DROP'|list<string>}> $alter of ["index type", "name", ["column definition", ...]] or ["index type", "name", "DROP"]
* @param list<array{string, string, 'DROP'|list<string>, 3?: string, 4?: string}> $alter of ["index type", "name", ["column definition", ...], "algorithm", "condition"] or ["index type", "name", "DROP"]
* @return Result|bool
*/
function alter_indexes(string $table, $alter) {
@@ -851,43 +893,34 @@ if (!defined('Adminer\DRIVER')) {
* @return Routine
*/
function routine(string $name, string $type): array {
$aliases = array("bool", "boolean", "integer", "double precision", "real", "dec", "numeric", "fixed", "national char", "national varchar");
$space = "(?:\\s|/\\*[\s\S]*?\\*/|(?:#|-- )[^\n]*\n?|--\r?\n)";
$enum = driver()->enumLength;
$type_pattern = "((" . implode("|", array_merge(array_keys(driver()->types()), $aliases)) . ")\\b(?:\\s*\\(((?:[^'\")]|$enum)++)\\))?"
. "\\s*(zerofill\\s*)?(unsigned(?:\\s+zerofill)?)?)(?:\\s*(?:CHARSET|CHARACTER\\s+SET)\\s*['\"]?([^'\"\\s,]+)['\"]?)?";
$pattern = "$space*(" . ($type == "FUNCTION" ? "" : driver()->inout) . ")?\\s*(?:`((?:[^`]|``)*)`\\s*|\\b(\\S+)\\s+)$type_pattern";
$create = get_val("SHOW CREATE $type " . idf_escape($name), 2);
preg_match("~\\(((?:$pattern\\s*,?)*)\\)\\s*" . ($type == "FUNCTION" ? "RETURNS\\s+$type_pattern\\s+" : "") . "(.*)~is", $create, $match);
$fields = array();
preg_match_all("~$pattern\\s*,?~is", $match[1], $matches, PREG_SET_ORDER);
foreach ($matches as $param) {
$fields[] = array(
"field" => str_replace("``", "`", $param[2]) . $param[3],
"type" => strtolower($param[5]),
"length" => preg_replace_callback("~$enum~s", 'Adminer\normalize_enum', $param[6]),
"unsigned" => strtolower(preg_replace('~\s+~', ' ', trim("$param[8] $param[7]"))),
"null" => true,
"full_type" => $param[4],
"inout" => strtoupper($param[1]),
"collation" => strtolower($param[9]),
);
$fields = get_rows("SELECT
PARAMETER_NAME field,
DATA_TYPE type,
CHARACTER_MAXIMUM_LENGTH length,
REGEXP_REPLACE(DTD_IDENTIFIER, '^[^ ]+ ', '') `unsigned`,
1 `null`,
DTD_IDENTIFIER full_type,
PARAMETER_MODE `inout`,
CHARACTER_SET_NAME collation
FROM information_schema.PARAMETERS
WHERE SPECIFIC_SCHEMA = DATABASE() AND ROUTINE_TYPE = '$type' AND SPECIFIC_NAME = " . q($name) . "
ORDER BY ORDINAL_POSITION");
$return = connection()->query("SELECT ROUTINE_COMMENT comment, ROUTINE_DEFINITION definition, 'SQL' language
FROM information_schema.ROUTINES
WHERE ROUTINE_SCHEMA = DATABASE() AND ROUTINE_TYPE = '$type' AND ROUTINE_NAME = " . q($name))->fetch_assoc();
if ($fields && $fields[0]['field'] == '') {
$return['returns'] = array_shift($fields);
}
return array(
"fields" => $fields,
"comment" => get_val("SELECT ROUTINE_COMMENT FROM information_schema.ROUTINES WHERE ROUTINE_SCHEMA = DATABASE() AND ROUTINE_NAME = " . q($name)),
) + ($type != "FUNCTION" ? array("definition" => $match[11]) : array(
"returns" => array("type" => $match[12], "length" => $match[13], "unsigned" => $match[15], "collation" => $match[16]),
"definition" => $match[17],
"language" => "SQL", // available in information_schema.ROUTINES.BODY_STYLE
));
$return['fields'] = $fields;
/** @phpstan-var Routine */
return $return;
}
/** Get list of routines
* @return list<string[]> ["SPECIFIC_NAME" => , "ROUTINE_NAME" => , "ROUTINE_TYPE" => , "DTD_IDENTIFIER" => ]
*/
function routines(): array {
return get_rows("SELECT ROUTINE_NAME AS SPECIFIC_NAME, ROUTINE_NAME, ROUTINE_TYPE, DTD_IDENTIFIER FROM information_schema.ROUTINES WHERE ROUTINE_SCHEMA = DATABASE()");
return get_rows("SELECT SPECIFIC_NAME, ROUTINE_NAME, ROUTINE_TYPE, DTD_IDENTIFIER FROM information_schema.ROUTINES WHERE ROUTINE_SCHEMA = DATABASE()");
}
/** Get list of available routine languages
@@ -942,8 +975,17 @@ if (!defined('Adminer\DRIVER')) {
}
/** Get SQL command to change database */
function use_sql(string $database): string {
return "USE " . idf_escape($database);
function use_sql(string $database, string $style = ""): string {
$name = idf_escape($database);
$return = "";
if (preg_match('~CREATE~', $style) && ($create = get_val("SHOW CREATE DATABASE $name", 1))) {
set_utf8mb4($create);
if ($style == "DROP+CREATE") {
$return = "DROP DATABASE IF EXISTS $name;\n";
}
$return .= "$create;\n";
}
return $return . "USE $name";
}
/** Get SQL commands to create triggers */
@@ -1011,18 +1053,26 @@ if (!defined('Adminer\DRIVER')) {
}
/** Check whether a feature is supported
* @param literal-string $feature "check|comment|copy|database|descidx|drop_col|dump|event|indexes|kill|materializedview|partitioning|privileges|procedure|processlist|routine|scheme|sequence|status|table|trigger|type|variables|view|view_trigger"
* @param literal-string $feature check|comment|columns|copy|database|descidx|drop_col|dump|event|indexes|kill|materializedview
* |move_col|privileges|procedure|processlist|routine|scheme|sequence|sql|status|table|trigger|type|variables|view|view_trigger
*/
function support(string $feature): bool {
return !preg_match("~scheme|sequence|type|view_trigger|materializedview" . (min_version(8) ? "" : "|descidx" . (min_version(5.1) ? "" : "|event|partitioning")) . (min_version('8.0.16', '10.2.1') ? "" : "|check") . "~", $feature);
return preg_match(
'~^(comment|columns|copy|database|drop_col|dump|indexes|kill|privileges|move_col|procedure|processlist|routine|sql|status|table|trigger|variables|view'
. (min_version(5.1) ? '|event' : '')
. (min_version(8) ? '|descidx' : '')
. (min_version('8.0.16', '10.2.1') ? '|check' : '')
. ')$~',
$feature
);
}
/** Kill a process
* @param numeric-string $val
* @param numeric-string $id
* @return Result|bool
*/
function kill_process(string $val) {
return queries("KILL " . number($val));
function kill_process(string $id) {
return queries("KILL " . number($id));
}
/** Return query to get connection ID */

View File

@@ -5,6 +5,7 @@ add_driver("oracle", "Oracle (beta)");
if (isset($_GET["oracle"])) {
define('Adminer\DRIVER', "oracle");
if (extension_loaded("oci8") && $_GET["ext"] != "pdo") {
class Db extends SqlDb {
public $extension = "oci8";
@@ -19,7 +20,7 @@ if (isset($_GET["oracle"])) {
$this->error = $error;
}
function attach(?string $server, string $username, string $password): string {
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);
@@ -59,6 +60,10 @@ if (isset($_GET["oracle"])) {
}
return $return;
}
function timeout(int $ms): bool {
return oci_set_call_timeout($this->link, $ms);
}
}
class Result {
@@ -105,7 +110,7 @@ if (isset($_GET["oracle"])) {
public $extension = "PDO_OCI";
public $_current_db;
function attach(?string $server, string $username, string $password): string {
function attach(string $server, string $username, string $password): string {
return $this->dsn("oci:dbname=//$server;charset=AL32UTF8", $username, $password);
}
@@ -164,7 +169,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)) && $this->conn->affected_rows)
|| queries("INSERT INTO " . table($table) . " (" . implode(", ", array_keys($set)) . ") VALUES (" . implode(", ", $set) . ")"))
) {
return false;
@@ -390,7 +395,6 @@ ORDER BY ac.constraint_type, aic.column_position", $connection2) as $row
$queries = array();
foreach ($alter as $val) {
if ($val[0] != "INDEX") {
//! descending UNIQUE indexes results in syntax error
$val[2] = preg_replace('~ DESC$~', '', $val[2]);
$create = ($val[2] == "DROP"
? "\nDROP CONSTRAINT " . idf_escape($val[1])

View File

@@ -5,6 +5,7 @@ add_driver("pgsql", "PostgreSQL");
if (isset($_GET["pgsql"])) {
define('Adminer\DRIVER', "pgsql");
if (extension_loaded("pgsql") && $_GET["ext"] != "pdo") {
class PgsqlDb extends SqlDb {
public $extension = "PgSQL";
@@ -19,10 +20,11 @@ if (isset($_GET["pgsql"])) {
$this->error = $error;
}
function attach(?string $server, string $username, string $password): string {
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, "'\\") . "'";
list($host, $port) = host_port(addcslashes($server, "'\\"));
$this->string = "host='$host'" . ($port ? " port='$port'" : "") . " user='" . addcslashes($username, "'\\") . "' password='" . addcslashes($password, "'\\") . "'";
$ssl = adminer()->connectSsl();
if (isset($ssl["mode"])) {
$this->string .= " sslmode='" . $ssl["mode"] . "'";
@@ -94,8 +96,9 @@ if (isset($_GET["pgsql"])) {
*/
function copyFrom(string $table, array $rows): bool {
$this->error = '';
set_error_handler(function ($errno, $error) {
set_error_handler(function (int $errno, string $error): bool {
$this->error = (ini_bool('html_errors') ? html_entity_decode($error) : $error);
return true;
});
$return = pg_copy_from($this->link, $table, $rows);
restore_error_handler();
@@ -125,8 +128,9 @@ if (isset($_GET["pgsql"])) {
$return = new \stdClass;
$return->orgtable = pg_field_table($this->result, $column);
$return->name = pg_field_name($this->result, $column);
$return->type = pg_field_type($this->result, $column); //! map to MySQL numbers
$return->charsetnr = ($return->type == "bytea" ? 63 : 0); // 63 - binary
$type = pg_field_type($this->result, $column);
$return->type = (preg_match(number_type(), $type) ? 0 : 15);
$return->charsetnr = ($type == "bytea" ? 63 : 0); // 63 - binary
return $return;
}
@@ -140,10 +144,11 @@ if (isset($_GET["pgsql"])) {
public $extension = "PDO_PgSQL";
public $timeout = 0;
function attach(?string $server, string $username, string $password): string {
function attach(string $server, string $username, string $password): string {
$db = adminer()->database();
list($host, $port) = host_port(addcslashes($server, "'\\"));
//! 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") . "'";
$dsn = "pgsql:host='$host'" . ($port ? " port='$port'" : "") . " client_encoding=utf8 dbname='" . ($db != "" ? addcslashes($db, "'\\") : "postgres") . "'";
$ssl = adminer()->connectSsl();
if (isset($ssl["mode"])) {
$dsn .= " sslmode='" . $ssl["mode"] . "'";
@@ -201,11 +206,13 @@ if (isset($_GET["pgsql"])) {
static $extensions = array("PgSQL", "PDO_PgSQL");
static $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 $operators = array("=", "<", ">", "<=", ">=", "!=", "~", "!~", "LIKE", "LIKE %%", "ILIKE", "ILIKE %%", "IN", "IS NULL", "NOT LIKE", "NOT ILIKE", "NOT IN", "IS NOT NULL", "SQL"); //! SQL - same-site CSRF
public $functions = array("char_length", "lower", "round", "to_hex", "to_timestamp", "upper");
public $grouping = array("avg", "count", "count distinct", "max", "min", "sum");
static function connect(?string $server, string $username, string $password) {
public string $nsOid = "(SELECT oid FROM pg_namespace WHERE nspname = current_schema())";
static function connect(string $server, string $username, string $password) {
$connection = parent::connect($server, $username, $password);
if (is_string($connection)) {
return $connection;
@@ -250,6 +257,10 @@ if (isset($_GET["pgsql"])) {
if (min_version(12, 0, $connection)) {
$this->generated = array("STORED");
}
$this->partitionBy = array("RANGE", "LIST");
if (!$connection->flavor) {
$this->partitionBy[] = "HASH";
}
}
function enumLength(array $field) {
@@ -279,7 +290,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)) && $this->conn->affected_rows)
|| queries("INSERT INTO " . table($table) . " (" . implode(", ", array_keys($set)) . ") VALUES (" . implode(", ", $set) . ")"))
) {
return false;
@@ -322,6 +333,39 @@ if (isset($_GET["pgsql"])) {
}
}
function inheritsFrom(string $table): array {
return get_rows("SELECT relname AS table, nspname AS ns FROM pg_class JOIN pg_inherits ON inhparent = oid JOIN pg_namespace ON relnamespace = pg_namespace.oid WHERE inhrelid = " . $this->tableOid($table) . " ORDER BY 2, 1");
}
function inheritedTables(string $table): array {
return get_rows("SELECT relname AS table, nspname AS ns FROM pg_inherits JOIN pg_class ON inhrelid = oid JOIN pg_namespace ON relnamespace = pg_namespace.oid WHERE inhparent = " . $this->tableOid($table) . " ORDER BY 2, 1");
}
function partitionsInfo(string $table): array {
$row = (min_version(10) ? $this->conn->query("SELECT * FROM pg_partitioned_table WHERE partrelid = " . $this->tableOid($table))->fetch_assoc() : null);
if ($row) {
$attrs = get_vals("SELECT attname FROM pg_attribute WHERE attrelid = $row[partrelid] AND attnum IN (" . str_replace(" ", ", ", $row["partattrs"]) . ")"); //! ordering
$by = array('h' => 'HASH', 'l' => 'LIST', 'r' => 'RANGE');
return array(
"partition_by" => $by[$row["partstrat"]],
"partition" => implode(", ", array_map('Adminer\idf_escape', $attrs)),
);
}
return array();
}
function tableOid(string $table): string {
return "(SELECT oid FROM pg_class WHERE relnamespace = $this->nsOid AND relname = " . q($table) . " AND relkind IN ('r', 'm', 'v', 'f', 'p'))";
}
function indexAlgorithms(array $tableStatus): array {
static $return = array();
if (!$return) {
$return = get_vals("SELECT amname FROM pg_am" . (min_version(9.6) ? " WHERE amtype = 'i'" : "") . " ORDER BY amname = '" . ($this->conn->flavor == 'cockroach' ? "prefix" : "btree") . "' DESC, amname");
}
return $return;
}
function supportsIndex(array $table_status): bool {
// returns true for "materialized view"
return $table_status["Engine"] != "view";
@@ -404,18 +448,19 @@ ORDER BY 1";
$return = array();
foreach (
get_rows("SELECT
c.relname AS \"Name\",
CASE c.relkind WHEN 'r' THEN 'table' WHEN 'm' THEN 'materialized view' ELSE 'view' END AS \"Engine\"" . ($has_size ? ",
relname AS \"Name\",
CASE relkind WHEN 'v' THEN 'view' WHEN 'm' THEN 'materialized view' ELSE 'table' END AS \"Engine\"" . ($has_size ? ",
pg_table_size(c.oid) AS \"Data_length\",
pg_indexes_size(c.oid) AS \"Index_length\"" : "") . ",
obj_description(c.oid, 'pg_class') AS \"Comment\",
" . (min_version(12) ? "''" : "CASE WHEN c.relhasoids THEN 'oid' ELSE '' END") . " AS \"Oid\",
c.reltuples as \"Rows\",
n.nspname
" . (min_version(12) ? "''" : "CASE WHEN relhasoids THEN 'oid' ELSE '' END") . " AS \"Oid\",
reltuples AS \"Rows\",
" . (min_version(10) ? "relispartition::int AS partition," : "") . "
current_schema() AS nspname
FROM pg_class c
JOIN pg_namespace n ON(n.nspname = current_schema() AND n.oid = c.relnamespace)
WHERE relkind IN ('r', 'm', 'v', 'f', 'p')
" . ($name != "" ? "AND relname = " . q($name) : "ORDER BY relname")) as $row //! Index_length, Auto_increment
AND relnamespace = " . driver()->nsOid . "
" . ($name != "" ? "AND relname = " . q($name) : "ORDER BY relname")) as $row //! Auto_increment
) {
$return[$row["Name"]] = $row;
}
@@ -442,20 +487,19 @@ WHERE relkind IN ('r', 'm', 'v', 'f', 'p')
format_type(a.atttypid, a.atttypmod) AS full_type,
pg_get_expr(d.adbin, d.adrelid) AS default,
a.attnotnull::int,
col_description(c.oid, a.attnum) AS comment" . (min_version(10) ? ",
i.indrelid AS primary,
col_description(a.attrelid, a.attnum) AS comment" . (min_version(10) ? ",
a.attidentity" . (min_version(12) ? ",
a.attgenerated" : "") : "") . "
FROM pg_class c
JOIN pg_namespace n ON c.relnamespace = n.oid
JOIN pg_attribute a ON c.oid = a.attrelid
LEFT JOIN pg_attrdef d ON c.oid = d.adrelid AND a.attnum = d.adnum
WHERE c.relname = " . q($table) . "
AND n.nspname = current_schema()
FROM pg_attribute a
LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
LEFT JOIN pg_index i ON a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey) AND i.indisprimary
WHERE a.attrelid = " . driver()->tableOid($table) . "
AND NOT a.attisdropped
AND a.attnum > 0
ORDER BY a.attnum") as $row
) {
//! collation, primary
//! collation
preg_match('~([^([]+)(\((.*)\))?([a-z ]+)?((\[[0-9]*])*)$~', $row["full_type"], $match);
list(, $type, $length, $row["length"], $addon, $array) = $match;
$row["length"] .= $array;
@@ -475,7 +519,7 @@ ORDER BY a.attnum") as $row
$row["auto_increment"] = $row['attidentity'] || preg_match('~^nextval\(~i', $row["default"])
|| preg_match('~^unique_rowid\(~', $row["default"]); // CockroachDB
$row["privileges"] = array("insert" => 1, "select" => 1, "update" => 1, "where" => 1, "order" => 1);
if (preg_match('~(.+)::[^,)]+(.*)~', $row["default"], $match)) {
if (!$row['generated'] && preg_match('~(.+)::[^,)]+(.*)~', $row["default"], $match)) {
$row["default"] = ($match[1] == "NULL" ? null : idf_unescape($match[1]) . $match[2]);
}
$return[$row["field"]] = $row;
@@ -486,25 +530,28 @@ ORDER BY a.attnum") as $row
function indexes($table, $connection2 = null) {
$connection2 = connection($connection2);
$return = array();
$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);
$table_oid = driver()->tableOid($table);
$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
FROM pg_index i, pg_class ci
WHERE i.indrelid = $table_oid AND ci.oid = i.indexrelid
get_rows("SELECT relname, indisunique::int, indisprimary::int, indkey, indoption, amname, pg_get_expr(indpred, indrelid, true) AS partial, pg_get_expr(indexprs, indrelid) AS indexpr
FROM pg_index
JOIN pg_class ON indexrelid = oid
JOIN pg_am ON pg_am.oid = pg_class.relam
WHERE indrelid = $table_oid
ORDER BY indisprimary DESC, indisunique DESC", $connection2) as $row
) {
$relname = $row["relname"];
$return[$relname]["type"] = ($row["indispartial"] ? "INDEX" : ($row["indisprimary"] ? "PRIMARY" : ($row["indisunique"] ? "UNIQUE" : "INDEX")));
$return[$relname]["type"] = ($row["indisprimary"] ? "PRIMARY" : ($row["indisunique"] ? "UNIQUE" : "INDEX"));
$return[$relname]["columns"] = array();
$return[$relname]["descs"] = array();
if ($row["indkey"]) {
foreach (explode(" ", $row["indkey"]) as $indkey) {
$return[$relname]["columns"][] = $columns[$indkey];
}
foreach (explode(" ", $row["indoption"]) as $indoption) {
$return[$relname]["descs"][] = (intval($indoption) & 1 ? '1' : null); // 1 - INDOPTION_DESC
}
$return[$relname]["algorithm"] = $row["amname"];
$return[$relname]["partial"] = $row["partial"];
$indexpr = preg_split('~(?<=\)), (?=\()~', $row["indexpr"]); //! '), (' used in expression
foreach (explode(" ", $row["indkey"]) as $indkey) {
$return[$relname]["columns"][] = ($indkey ? $columns[$indkey] : array_shift($indexpr));
}
foreach (explode(" ", $row["indoption"]) as $indoption) {
$return[$relname]["descs"][] = (intval($indoption) & 1 ? '1' : null); // 1 - INDOPTION_DESC
}
$return[$relname]["lengths"] = array();
}
@@ -514,12 +561,13 @@ ORDER BY indisprimary DESC, indisunique DESC", $connection2) as $row
function foreign_keys($table) {
$return = array();
foreach (
get_rows("SELECT conname, condeferrable::int AS deferrable, pg_get_constraintdef(oid) AS definition
get_rows("SELECT conname, condeferrable::int AS deferrable, condeferred::int AS deferred, pg_get_constraintdef(oid) AS definition
FROM pg_constraint
WHERE conrelid = (SELECT pc.oid FROM pg_class AS pc INNER JOIN pg_namespace AS pn ON (pn.oid = pc.relnamespace) WHERE pc.relname = " . q($table) . " AND pn.nspname = current_schema())
WHERE conrelid = " . driver()->tableOid($table) . "
AND contype = 'f'::char
ORDER BY conkey, conname") as $row
) {
$row['deferrable'] = ($row['deferrable'] ? '' : 'NOT ') . 'DEFERRABLE' . ($row['deferred'] ? ' INITIALLY DEFERRED' : '');
if (preg_match('~FOREIGN KEY\s*\((.+)\)\s*REFERENCES (.+)\((.+)\)(.*)$~iA', $row['definition'], $match)) {
$row['source'] = array_map('Adminer\idf_unescape', array_map('trim', explode(',', $match[1])));
if (preg_match('~^(("([^"]|"")+"|[^"]+)\.)?"?("([^"]|"")+"|[^"]+)$~', $match[2], $match2)) {
@@ -527,8 +575,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;
}
}
@@ -536,7 +584,7 @@ ORDER BY conkey, conname") as $row
}
function view($name) {
return array("select" => trim(get_val("SELECT pg_get_viewdef(" . get_val("SELECT oid FROM pg_class WHERE relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = current_schema()) AND relname = " . q($name)) . ")")));
return array("select" => trim(get_val("SELECT pg_get_viewdef(" . driver()->tableOid($name) . ")")));
}
function collations() {
@@ -619,7 +667,31 @@ ORDER BY conkey, conname") as $row
}
$alter = array_merge($alter, $foreign);
if ($table == "") {
array_unshift($queries, "CREATE TABLE " . table($name) . " (\n" . implode(",\n", $alter) . "\n)");
$status = "";
if ($partitioning) {
$cockroach = (connection()->flavor == 'cockroach');
$status = " PARTITION BY $partitioning[partition_by]($partitioning[partition])";
if ($partitioning["partition_by"] == 'HASH') {
$partitions = +$partitioning["partitions"];
for ($i=0; $i < $partitions; $i++) {
$queries[] = "CREATE TABLE " . idf_escape($name . "_$i") . " PARTITION OF " . idf_escape($name) . " FOR VALUES WITH (MODULUS $partitions, REMAINDER $i)";
}
} else {
$prev = "MINVALUE";
foreach ($partitioning["partition_names"] as $i => $val) {
$value = $partitioning["partition_values"][$i];
$partition = " VALUES " . ($partitioning["partition_by"] == 'LIST' ? "IN ($value)" : "FROM ($prev) TO ($value)");
if ($cockroach) {
$status .= ($i ? "," : " (") . "\n PARTITION " . (preg_match('~^DEFAULT$~i', $val) ? $val : idf_escape($val)) . "$partition";
} else {
$queries[] = "CREATE TABLE " . idf_escape($name . "_$val") . " PARTITION OF " . idf_escape($name) . " FOR$partition";
}
$prev = $value;
}
$status .= ($cockroach ? "\n)" : "");
}
}
array_unshift($queries, "CREATE TABLE " . table($name) . " (\n" . implode(",\n", $alter) . "\n)$status");
} elseif ($alter) {
array_unshift($queries, "ALTER TABLE " . table($table) . "\n" . implode(",\n", $alter));
}
@@ -646,7 +718,7 @@ ORDER BY conkey, conname") as $row
$queries = array();
foreach ($alter as $val) {
if ($val[0] != "INDEX") {
//! descending UNIQUE indexes results in syntax error
//! descending UNIQUE indexes result in syntax error
$create[] = ($val[2] == "DROP"
? "\nDROP CONSTRAINT " . idf_escape($val[1])
: "\nADD" . ($val[1] != "" ? " CONSTRAINT " . idf_escape($val[1]) : "") . " $val[0] " . ($val[0] == "PRIMARY" ? "KEY " : "") . "(" . implode(", ", $val[2]) . ")"
@@ -654,7 +726,12 @@ ORDER BY conkey, conname") as $row
} elseif ($val[2] == "DROP") {
$drop[] = idf_escape($val[1]);
} else {
$queries[] = "CREATE INDEX " . idf_escape($val[1] != "" ? $val[1] : uniqid($table . "_")) . " ON " . table($table) . " (" . implode(", ", $val[2]) . ")";
$queries[] = "CREATE INDEX " . idf_escape($val[1] != "" ? $val[1] : uniqid($table . "_"))
. " ON " . table($table)
. ($val[3] ? " USING $val[3]" : "")
. " (" . implode(", ", $val[2]) . ")"
. ($val[4] ? " WHERE $val[4]" : "")
;
}
}
if ($create) {
@@ -750,7 +827,7 @@ FROM information_schema.routines
WHERE routine_schema = current_schema() AND specific_name = ' . q($name));
$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
$return["fields"] = get_rows('SELECT COALESCE(parameter_name, ordinal_position::text) AS field, data_type AS type, character_maximum_length AS length, parameter_mode AS inout
FROM information_schema.parameters
WHERE specific_schema = current_schema() AND specific_name = ' . q($name) . '
ORDER BY ordinal_position');
@@ -796,7 +873,7 @@ ORDER BY SPECIFIC_NAME');
return get_key_vals(
"SELECT oid, typname
FROM pg_type
WHERE typnamespace = (SELECT oid FROM pg_namespace WHERE nspname = current_schema())
WHERE typnamespace = " . driver()->nsOid . "
AND typtype IN ('b','d','e')
AND typelem = 0"
);
@@ -836,7 +913,7 @@ AND typelem = 0"
ksort($fkeys);
foreach ($fkeys as $fkey_name => $fkey) {
$return .= "ALTER TABLE ONLY " . idf_escape($status['nspname']) . "." . idf_escape($status['Name']) . " ADD CONSTRAINT " . idf_escape($fkey_name) . " $fkey[definition] " . ($fkey['deferrable'] ? 'DEFERRABLE' : 'NOT DEFERRABLE') . ";\n";
$return .= "ALTER TABLE ONLY " . idf_escape($status['nspname']) . "." . idf_escape($status['Name']) . " ADD CONSTRAINT " . idf_escape($fkey_name) . " $fkey[definition];\n";
}
return ($return ? "$return\n" : $return);
@@ -847,9 +924,10 @@ AND typelem = 0"
$sequences = array();
$status = table_status1($table);
$ns = idf_escape($status['nspname']);
if (is_view($status)) {
$view = view($table);
return rtrim("CREATE VIEW " . idf_escape($table) . " AS $view[select]", ";");
return rtrim("CREATE VIEW $ns." . idf_escape($table) . " AS $view[select]", ";");
}
$fields = fields($table);
@@ -857,7 +935,7 @@ AND typelem = 0"
return false;
}
$return = "CREATE TABLE " . idf_escape($status['nspname']) . "." . idf_escape($status['Name']) . " (\n ";
$return = "CREATE TABLE $ns." . idf_escape($status['Name']) . " (\n ";
// fields' definitions
foreach ($fields as $field) {
@@ -873,8 +951,8 @@ AND typelem = 0"
? "SELECT *, cache_size AS cache_value FROM pg_sequences WHERE schemaname = current_schema() AND sequencename = " . q(idf_unescape($sequence_name))
: "SELECT * FROM $sequence_name"
), null, "-- "));
$sequences[] = ($style == "DROP+CREATE" ? "DROP SEQUENCE IF EXISTS $sequence_name;\n" : "")
. "CREATE SEQUENCE $sequence_name INCREMENT $sq[increment_by] MINVALUE $sq[min_value] MAXVALUE $sq[max_value]"
$sequences[] = ($style == "DROP+CREATE" ? "DROP SEQUENCE IF EXISTS $ns.$sequence_name;\n" : "")
. "CREATE SEQUENCE $ns.$sequence_name INCREMENT $sq[increment_by] MINVALUE $sq[min_value] MAXVALUE $sq[max_value]"
. ($auto_increment && $sq['last_value'] ? " START " . ($sq["last_value"] + 1) : "")
. " CACHE $sq[cache_value];"
;
@@ -895,19 +973,27 @@ AND typelem = 0"
}
foreach (driver()->checkConstraints($table) as $conname => $consrc) {
$return_parts[] = "CONSTRAINT " . idf_escape($conname) . " CHECK $consrc";
$return_parts[] = "CONSTRAINT " . idf_escape($conname) . " CHECK ($consrc)";
}
$return .= implode(",\n ", $return_parts) . "\n)";
$return .= implode(",\n ", $return_parts) . "\n) WITH (oids = " . ($status['Oid'] ? 'true' : 'false') . ");";
$partition = driver()->partitionsInfo($status['Name']);
if ($partition) {
$return .= "\nPARTITION BY $partition[partition_by]($partition[partition])";
}
//! parse pg_class.relpartbound to create PARTITION OF
//! don't insert partitioned data twice
$return .= "\nWITH (oids = " . ($status['Oid'] ? 'true' : 'false') . ");";
// comments for table & fields
if ($status['Comment']) {
$return .= "\n\nCOMMENT ON TABLE " . idf_escape($status['nspname']) . "." . idf_escape($status['Name']) . " IS " . q($status['Comment']) . ";";
$return .= "\n\nCOMMENT ON TABLE $ns." . idf_escape($status['Name']) . " IS " . q($status['Comment']) . ";";
}
foreach ($fields as $field_name => $field) {
if ($field['comment']) {
$return .= "\n\nCOMMENT ON COLUMN " . idf_escape($status['nspname']) . "." . idf_escape($status['Name']) . "." . idf_escape($field_name) . " IS " . q($field['comment']) . ";";
$return .= "\n\nCOMMENT ON COLUMN $ns." . idf_escape($status['Name']) . "." . idf_escape($field_name) . " IS " . q($field['comment']) . ";";
}
}
@@ -933,8 +1019,16 @@ AND typelem = 0"
}
function use_sql($database) {
return "\connect " . idf_escape($database);
function use_sql($database, $style = "") {
$name = idf_escape($database);
$return = "";
if (preg_match('~CREATE~', $style)) {
if ($style == "DROP+CREATE") {
$return = "DROP DATABASE IF EXISTS $name;\n";
}
$return .= "CREATE DATABASE $name;\n"; //! get info from pg_database
}
return "$return\\connect $name";
}
function show_variables() {
@@ -953,9 +1047,11 @@ AND typelem = 0"
}
function support($feature) {
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'
return preg_match('~^(check|columns|comment|database|drop_col|dump|descidx|indexes|kill|partial_indexes|routine|scheme|sequence|sql|table|trigger|type|variables|view'
. (min_version(9.3) ? '|materializedview' : '')
. (min_version(11) ? '|procedure' : '')
. (connection()->flavor == 'cockroach' ? '' : '|processlist') // https://github.com/cockroachdb/cockroach/issues/24745
. '|kill|dump)$~', $feature)
. ')$~', $feature)
;
}

View File

@@ -5,13 +5,13 @@ add_driver("sqlite", "SQLite");
if (isset($_GET["sqlite"])) {
define('Adminer\DRIVER', "sqlite");
if (class_exists("SQLite3") && $_GET["ext"] != "pdo") {
if (class_exists("SQLite3") && $_GET["ext"] != "pdo") {
abstract class SqliteDb extends SqlDb {
public $extension = "SQLite3";
private $link;
function attach(?string $filename, string $username, string $password): string {
function attach(string $filename, string $username, string $password): string {
$this->link = new \SQLite3($filename);
$version = $this->link->version();
$this->server_info = $version["versionString"];
@@ -75,11 +75,8 @@ if (isset($_GET["sqlite"])) {
abstract class SqliteDb extends PdoDb {
public $extension = "PDO_SQLite";
function attach(?string $filename, string $username, string $password): string {
$this->dsn(DRIVER . ":$filename", "", "");
$this->query("PRAGMA foreign_keys = 1");
$this->query("PRAGMA busy_timeout = 500");
return '';
function attach(string $filename, string $username, string $password): string {
return $this->dsn(DRIVER . ":$filename", "", "");
}
}
@@ -87,7 +84,7 @@ if (isset($_GET["sqlite"])) {
if (class_exists('Adminer\SqliteDb')) {
class Db extends SqliteDb {
function attach(?string $filename, string $username, string $password): string {
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");
@@ -122,7 +119,7 @@ if (isset($_GET["sqlite"])) {
public $functions = array("hex", "length", "lower", "round", "unixepoch", "upper");
public $grouping = array("avg", "count", "count distinct", "group_concat", "max", "min", "sum");
static function connect(?string $server, string $username, string $password) {
static function connect(string $server, string $username, string $password) {
if ($password != "") {
return lang('Database does not support password.');
}
@@ -452,7 +449,7 @@ if (isset($_GET["sqlite"])) {
* @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 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
@@ -680,7 +677,7 @@ if (isset($_GET["sqlite"])) {
return "DELETE FROM " . table($table);
}
function use_sql($database) {
function use_sql($database, $style = "") {
}
function trigger_sql($table) {

View File

@@ -41,16 +41,9 @@ SET foreign_key_checks = 0;
foreach ((array) $databases as $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") {
echo "DROP DATABASE IF EXISTS " . idf_escape($db) . ";\n";
}
echo "$create;\n";
}
if ($is_sql) {
if ($style) {
echo use_sql($db) . ";\n\n";
echo use_sql($db, $style) . ";\n\n";
}
$out = "";

View File

@@ -9,7 +9,7 @@ $where = (isset($_GET["select"])
);
$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 ((!$update && !isset($field["privileges"]["insert"])) || adminer()->fieldName($field) == "") {
unset($fields[$name]);
}
}
@@ -65,9 +65,7 @@ if ($_POST && !$error && !isset($_GET["select"])) {
}
$row = null;
if ($_POST["save"]) {
$row = (array) $_POST["fields"];
} elseif ($where) {
if ($where) {
$select = array();
foreach ($fields as $name => $field) {
if (isset($field["privileges"]["select"])) {
@@ -113,4 +111,8 @@ if (!support("table") && !$fields) { // used by Mongo and SimpleDB
}
}
if ($_POST["save"]) {
$row = (array) $_POST["fields"] + ($row ? $row : array());
}
edit_form($TABLE, $fields, $row, $update, $error);

View File

@@ -11,7 +11,7 @@ if (substr(VERSION, -4) != '-dev') {
header("Cache-Control: immutable");
}
@ini_set("zlib.output_compression", 1); // @ - may be disabled
@ini_set("zlib.output_compression", '1'); // @ - may be disabled
if ($_GET["file"] == "default.css") {
header("Content-Type: text/css; charset=utf-8");
@@ -34,7 +34,8 @@ if ($_GET["file"] == "default.css") {
../externals/jush/modules/jush-sqlite.js;
../externals/jush/modules/jush-mssql.js;
../externals/jush/modules/jush-oracle.js;
../externals/jush/modules/jush-simpledb.js', 'minify_js'));
../externals/jush/modules/jush-simpledb.js;
../externals/jush/modules/jush-igdb.js', 'minify_js'));
} elseif ($_GET["file"] == "logo.png") {
header("Content-Type: image/png");
echo compile_file('../adminer/static/logo.png');

View File

@@ -31,7 +31,7 @@ if ($_POST && !$error && !$_POST["add"] && !$_POST["change"] && !$_POST["change-
$result
);
if (!$row["drop"]) {
$error = "$error<br>" . lang('Source and target columns must have the same data type, there must be an index on the target columns and referenced data must exist.'); //! no partitioning
$error = lang('Source and target columns must have the same data type, there must be an index on the target columns and referenced data must exist.'); //! no partitioning
}
}
@@ -103,10 +103,11 @@ foreach ($row["source"] as $key => $val) {
<p>
<label><?php echo lang('ON DELETE'); ?>: <?php echo html_select("on_delete", array(-1 => "") + explode("|", driver()->onActions), $row["on_delete"]); ?></label>
<label><?php echo lang('ON UPDATE'); ?>: <?php echo html_select("on_update", array(-1 => "") + explode("|", driver()->onActions), $row["on_update"]); ?></label>
<?php echo (DRIVER === 'pgsql' ? html_select("deferrable", array('NOT DEFERRABLE', 'DEFERRABLE', 'DEFERRABLE INITIALLY DEFERRED'), $row["deferrable"]) . ' ' : ''); ?>
<?php echo doc_link(array(
'sql' => "innodb-foreign-key-constraints.html",
'mariadb' => "foreign-keys/",
'pgsql' => "sql-createtable.html#SQL-CREATETABLE-REFERENCES",
'pgsql' => "sql-createtable.html#SQL-CREATETABLE-PARMS-REFERENCES",
'mssql' => "t-sql/statements/create-table-transact-sql",
'oracle' => "SQLRF01111",
)); ?>

View File

@@ -60,6 +60,10 @@ class Adminer {
return get_databases($flush);
}
/** Print links after list of plugins */
function pluginsLinks(): void {
}
/** Operators used in select
* @return list<string> operators
*/
@@ -81,6 +85,10 @@ class Adminer {
return 2;
}
/** Called after connecting and selecting a database */
function afterConnect(): void {
}
/** Headers to send before HTML output */
function headers(): void {
}
@@ -104,15 +112,24 @@ class Adminer {
return true;
}
/** Print extra classes in <body class>; must start with a space */
function bodyClass(): void {
echo " adminer";
}
/** Get URLs of the CSS files
* @return list<string>
* @return string[] key is URL, value is either 'light' (supports only light color scheme), 'dark' or '' (both)
*/
function css(): array {
$return = array();
foreach (array("", "-dark") as $mode) {
$filename = "adminer$mode.css";
if (file_exists($filename)) {
$return[] = "$filename?v=" . crc32(file_get_contents($filename));
$file = file_get_contents($filename);
$return["$filename?v=" . crc32($file)] = ($mode
? "dark"
: (preg_match('~prefers-color-scheme:\s*dark~', $file) ? '' : 'light')
);
}
}
return $return;
@@ -123,7 +140,7 @@ class Adminer {
echo "<table class='layout'>\n";
// this is matched by compile.php
echo adminer()->loginFormField('driver', '<tr><th>' . lang('System') . '<td>', html_select("auth[driver]", SqlDriver::$drivers, DRIVER, "loginDriver(this);"));
echo adminer()->loginFormField('server', '<tr><th>' . lang('Server') . '<td>', '<input name="auth[server]" value="' . h(SERVER) . '" title="hostname[:port]" placeholder="localhost" autocapitalize="off">');
echo adminer()->loginFormField('server', '<tr><th>' . lang('Server') . '<td>', '<input name="auth[server]" value="' . h(SERVER) . '" title="' . lang('hostname[:port] or :socket') . '" placeholder="localhost" autocapitalize="off">');
// this is matched by compile.php
echo adminer()->loginFormField('username', '<tr><th>' . lang('Username') . '<td>', '<input name="auth[username]" id="username" autofocus value="' . h($_GET["username"]) . '" autocomplete="username" autocapitalize="off">' . script("const authDriver = qs('#username').form['auth[driver]']; authDriver && authDriver.onchange();"));
echo adminer()->loginFormField('password', '<tr><th>' . lang('Password') . '<td>', '<input type="password" name="auth[password]" autocomplete="current-password">');
@@ -175,6 +192,7 @@ class Adminer {
* @param ?string $set new item options, NULL for no new item
*/
function selectLinks(array $tableStatus, ?string $set = ""): void {
$name = $tableStatus["Name"];
echo '<p class="links">';
$links = array("select" => lang('Select data'));
if (support("table") || support("indexes")) {
@@ -184,15 +202,16 @@ class Adminer {
if (support("table")) {
$is_view = is_view($tableStatus);
if ($is_view) {
$links["view"] = lang('Alter view');
} else {
if (support("view")) {
$links["view"] = lang('Alter view');
}
} elseif (function_exists('Adminer\alter_table')) {
$links["create"] = lang('Alter table');
}
}
if ($set !== null) {
$links["edit"] = lang('New item');
}
$name = $tableStatus["Name"];
foreach ($links as $key => $val) {
echo " <a href='" . h(ME) . "$key=" . urlencode($name) . ($key == "edit" ? $set : "") . "'" . bold(isset($_GET[$key])) . ">$val</a>";
}
@@ -269,7 +288,7 @@ class Adminer {
/** Get a link to use in select table
* @param string $val raw value of the field
* @param Field $field
* @param array{type: string} $field
* @return string|void null to create the default link
*/
function selectLink(?string $val, array $field) {
@@ -278,7 +297,7 @@ class Adminer {
/** Value printed in select table
* @param ?string $val HTML-escaped value to print
* @param ?string $link link to foreign key
* @param Field $field
* @param array{type: string} $field
* @param string $original original value before applying editVal() and escaping
*/
function selectVal(?string $val, ?string $link, array $field, ?string $original): string {
@@ -287,14 +306,14 @@ class Adminer {
: (preg_match('~json~', $field["type"]) ? "<code class='jush-js'>$val</code>"
: $val)
));
if (preg_match('~blob|bytea|raw|file~', $field["type"]) && !is_utf8($val)) {
if (is_blob($field) && !is_utf8($val)) {
$return = "<i>" . lang('%d byte(s)', strlen($original)) . "</i>";
}
return ($link ? "<a href='" . h($link) . "'" . (is_url($link) ? target_blank() : "") . ">$return</a>" : $return);
}
/** Value conversion used in select and edit
* @param Field $field
* @param array{type: string} $field
*/
function editVal(?string $val, array $field): ?string {
return $val;
@@ -339,9 +358,15 @@ class Adminer {
/** Print list of indexes on table in tabular format
* @param Index[] $indexes
* @param TableStatus $tableStatus
*/
function tableIndexesPrint(array $indexes): void {
function tableIndexesPrint(array $indexes, array $tableStatus): void {
$partial = false;
foreach ($indexes as $name => $index) {
$partial |= !!$index["partial"];
}
echo "<table>\n";
$default_algorithm = first(driver()->indexAlgorithms($tableStatus));
foreach ($indexes as $name => $index) {
ksort($index["columns"]); // enforce correct columns order
$print = array();
@@ -351,7 +376,14 @@ class Adminer {
. ($index["descs"][$key] ? " DESC" : "")
;
}
echo "<tr title='" . h($name) . "'><th>$index[type]<td>" . implode(", ", $print) . "\n";
echo "<tr title='" . h($name) . "'>";
echo "<th>$index[type]" . ($default_algorithm && $index['algorithm'] != $default_algorithm ? " ($index[algorithm])" : "");
echo "<td>" . implode(", ", $print);
if ($partial) {
echo "<td>" . ($index['partial'] ? "<code class='jush-" . JUSH . "'>WHERE " . h($index['partial']) : "");
}
echo "\n";
}
echo "</table>\n";
}
@@ -391,9 +423,9 @@ class Adminer {
foreach ($indexes as $i => $index) {
if ($index["type"] == "FULLTEXT") {
echo "<div>(<i>" . implode("</i>, <i>", array_map('Adminer\h', $index["columns"])) . "</i>) AGAINST";
echo " <input type='search' name='fulltext[$i]' value='" . h($_GET["fulltext"][$i]) . "'>";
echo " <input type='search' name='fulltext[$i]' value='" . h(idx($_GET["fulltext"], $i)) . "'>";
echo script("qsl('input').oninput = selectFieldChange;", "");
echo checkbox("boolean[$i]", 1, isset($_GET["boolean"][$i]), "BOOL");
echo (JUSH == 'sql' ? checkbox("boolean[$i]", 1, isset($_GET["boolean"][$i]), "BOOL") : '');
echo "</div>\n";
}
}
@@ -529,46 +561,44 @@ class Adminer {
function selectSearchProcess(array $fields, array $indexes): array {
$return = array();
foreach ($indexes as $i => $index) {
if ($index["type"] == "FULLTEXT" && $_GET["fulltext"][$i] != "") {
if ($index["type"] == "FULLTEXT" && idx($_GET["fulltext"], $i) != "") {
$return[] = "MATCH (" . implode(", ", array_map('Adminer\idf_escape', $index["columns"])) . ") AGAINST (" . q($_GET["fulltext"][$i]) . (isset($_GET["boolean"][$i]) ? " IN BOOLEAN MODE" : "") . ")";
}
}
foreach ((array) $_GET["where"] as $key => $val) {
if ("$val[col]$val[val]" != "" && in_array($val["op"], adminer()->operators())) {
$prefix = "";
$cond = " $val[op]";
if (preg_match('~IN$~', $val["op"])) {
$in = process_length($val["val"]);
$cond .= " " . ($in != "" ? $in : "(NULL)");
} elseif ($val["op"] == "SQL") {
$cond = " $val[val]"; // SQL injection
} elseif ($val["op"] == "LIKE %%") {
$cond = " LIKE " . adminer()->processInput(idx($fields, $val["col"], array()), "%$val[val]%"); // this is used by search anywhere which doesn't set $val["col"]
} elseif ($val["op"] == "ILIKE %%") {
$cond = " ILIKE " . adminer()->processInput($fields[$val["col"]], "%$val[val]%");
} elseif ($val["op"] == "FIND_IN_SET") {
$prefix = "$val[op](" . q($val["val"]) . ", ";
$cond = ")";
} elseif (!preg_match('~NULL$~', $val["op"])) {
$cond .= " " . adminer()->processInput($fields[$val["col"]], $val["val"]);
}
if ($val["col"] != "") {
$return[] = $prefix . driver()->convertSearch(idf_escape($val["col"]), $val, $fields[$val["col"]]) . $cond;
} else {
// find anywhere
$cols = array();
foreach ($fields as $name => $field) {
if (
isset($field["privileges"]["where"])
&& (preg_match('~^[-\d.' . (preg_match('~IN$~', $val["op"]) ? ',' : '') . ']+$~', $val["val"]) || !preg_match('~' . number_type() . '|bit~', $field["type"]))
&& (!preg_match("~[\x80-\xFF]~", $val["val"]) || preg_match('~char|text|enum|set~', $field["type"]))
&& (!preg_match('~date|timestamp~', $field["type"]) || preg_match('~^\d+-\d+-\d+~', $val["val"]))
) {
$cols[] = $prefix . driver()->convertSearch(idf_escape($name), $val, $field) . $cond;
}
$col = $val["col"];
if ("$col$val[val]" != "" && in_array($val["op"], adminer()->operators())) {
$conds = array();
foreach (($col != "" ? array($col => $fields[$col]) : $fields) as $name => $field) {
$prefix = "";
$cond = " $val[op]";
if (preg_match('~IN$~', $val["op"])) {
$in = process_length($val["val"]);
$cond .= " " . ($in != "" ? $in : "(NULL)");
} elseif ($val["op"] == "SQL") {
$cond = " $val[val]"; // SQL injection
} elseif (preg_match('~^(I?LIKE) %%$~', $val["op"], $match)) {
$cond = " $match[1] " . adminer()->processInput($field, "%$val[val]%");
} elseif ($val["op"] == "FIND_IN_SET") {
$prefix = "$val[op](" . q($val["val"]) . ", ";
$cond = ")";
} elseif (!preg_match('~NULL$~', $val["op"])) {
$cond .= " " . adminer()->processInput($field, $val["val"]);
}
if ($col != "" || ( // find anywhere
isset($field["privileges"]["where"])
&& (preg_match('~^[-\d.' . (preg_match('~IN$~', $val["op"]) ? ',' : '') . ']+$~', $val["val"]) || !preg_match('~' . number_type() . '|bit~', $field["type"]))
&& (!preg_match("~[\x80-\xFF]~", $val["val"]) || preg_match('~char|text|enum|set~', $field["type"]))
&& (!preg_match('~date|timestamp~', $field["type"]) || preg_match('~^\d+-\d+-\d+~', $val["val"]))
)) {
$conds[] = $prefix . driver()->convertSearch(idf_escape($name), $val, $field) . $cond;
}
$return[] = ($cols ? "(" . implode(" OR ", $cols) . ")" : "1 = 0");
}
$return[] =
(count($conds) == 1 ? $conds[0] :
($conds ? "(" . implode(" OR ", $conds) . ")" :
"1 = 0"
));
}
}
return $return;
@@ -640,13 +670,13 @@ class Adminer {
}
$history[$_GET["db"]][] = array($query, time(), $time); // not DB - $_GET["db"] is changed in database.inc.php //! respect $_GET["ns"]
$sql_id = "sql-" . count($history[$_GET["db"]]);
$return = "<a href='#$sql_id' class='toggle'>" . lang('SQL command') . "</a>\n";
$return = "<a href='#$sql_id' class='toggle'>" . lang('SQL command') . "</a> <a href='' class='jsonly copy'>🗐</a>\n";
if (!$failed && ($warnings = driver()->warnings())) {
$id = "warnings-" . count($history[$_GET["db"]]);
$return = "<a href='#$id' class='toggle'>" . lang('Warnings') . "</a>, $return<div id='$id' class='hidden'>\n$warnings</div>\n";
}
return " <span class='time'>" . @date("H:i:s") . "</span>" // @ - time zone may be not set
. " $return<div id='$sql_id' class='hidden'><pre><code class='jush-" . JUSH . "'>" . shorten_utf8($query, 1000) . "</code></pre>"
. " $return<div id='$sql_id' class='hidden'><pre><code class='jush-" . JUSH . "'>" . shorten_utf8($query, 1e4) . "</code></pre>"
. ($time ? " <span class='time'>($time)</span>" : '')
. (support("sql") ? '<p><a href="' . h(str_replace("db=" . urlencode(DB), "db=" . urlencode($_GET["db"]), ME) . 'sql=&history=' . (count($history[$_GET["db"]]) - 1)) . '">' . lang('Edit') . '</a>' : '')
. '</div>'
@@ -675,7 +705,7 @@ class Adminer {
}
}
}
if ($key && $functions && !preg_match('~set|blob|bytea|raw|file|bool~', $field["type"])) {
if ($key && $functions && !preg_match('~set|bool~', $field["type"]) && !is_blob($field)) {
$return .= "/SQL";
}
}
@@ -689,13 +719,13 @@ class Adminer {
* @param ?string $table null in call.inc.php
* @param Field $field
* @param string $attrs attributes to use inside the tag
* @param string|string[]|false|null $value false means original value
* @return string custom input field or empty string for default
*/
function editInput(?string $table, array $field, string $attrs, ?string $value): string {
function editInput(?string $table, array $field, string $attrs, $value): string {
if ($field["type"] == "enum") {
return (isset($_GET["select"]) ? "<label><input type='radio'$attrs value='-1' checked><i>" . lang('original') . "</i></label> " : "")
. ($field["null"] ? "<label><input type='radio'$attrs value=''" . ($value !== null || isset($_GET["select"]) ? "" : " checked") . "><i>NULL</i></label> " : "")
. enum_input("radio", $attrs, $field, $value, $value === 0 ? 0 : null) // 0 - empty value
return (isset($_GET["select"]) ? "<label><input type='radio'$attrs value='orig' checked><i>" . lang('original') . "</i></label> " : "")
. enum_input("radio", $attrs, $field, $value, "NULL")
;
}
return "";
@@ -726,7 +756,7 @@ class Adminer {
} elseif (preg_match('~^([+-]|\|\|)$~', $function)) {
$return = idf_escape($name) . " $function $return";
} elseif (preg_match('~^[+-] interval$~', $function)) {
$return = idf_escape($name) . " $function " . (preg_match("~^(\\d+|'[0-9.: -]') [A-Z_]+\$~i", $value) ? $value : $return);
$return = idf_escape($name) . " $function " . (preg_match("~^(\\d+|'[0-9.: -]') [A-Z_]+\$~i", $value) && JUSH != "pgsql" ? $value : $return);
} elseif (preg_match('~^(addtime|subtime|concat)$~', $function)) {
$return = "$function(" . idf_escape($name) . ", $return)";
} elseif (preg_match('~^(md5|sha1|password|encrypt)$~', $function)) {
@@ -815,7 +845,7 @@ class Adminer {
}
}
}
$result = connection()->query($query, 1); // 1 - MYSQLI_USE_RESULT //! enum and set as numbers
$result = connection()->query($query, 1); // 1 - MYSQLI_USE_RESULT
if ($result) {
$insert = "";
$buffer = "";
@@ -891,7 +921,7 @@ class Adminer {
* @return string filename without extension
*/
function dumpFilename(string $identifier): string {
return friendly_url($identifier != "" ? $identifier : (SERVER != "" ? SERVER : "localhost"));
return friendly_url($identifier != "" ? $identifier : (SERVER ?: "localhost"));
}
/** Send headers for export
@@ -938,6 +968,12 @@ class Adminer {
echo (support("scheme") ? "<a href='" . h(ME) . "scheme='>" . ($_GET["ns"] != "" ? lang('Alter schema') : lang('Create schema')) . "</a>\n" : "");
echo ($_GET["ns"] !== "" ? '<a href="' . h(ME) . 'schema=">' . lang('Database schema') . "</a>\n" : "");
echo (support("privileges") ? "<a href='" . h(ME) . "privileges='>" . lang('Privileges') . "</a>\n" : "");
if ($_GET["ns"] !== "") {
echo (support("routine") ? "<a href='#routines'>" . lang('Routines') . "</a>\n" : "");
echo (support("sequence") ? "<a href='#sequences'>" . lang('Sequences') . "</a>\n" : "");
echo (support("type") ? "<a href='#user-types'>" . lang('User types') . "</a>\n" : "");
echo (support("event") ? "<a href='#events'>" . lang('Events') . "</a>\n" : "");
}
return true;
}
@@ -960,7 +996,7 @@ class Adminer {
if ($password !== null) {
$dbs = $_SESSION["db"][$vendor][$server][$username];
foreach (($dbs ? array_keys($dbs) : array("")) as $db) {
$output .= "<li><a href='" . h(auth_url($vendor, $server, $username, $db)) . "'>($name) " . h($username . ($server != "" ? "@" . adminer()->serverName($server) : "") . ($db != "" ? " - $db" : "")) . "</a>\n";
$output .= "<li><a href='" . h(auth_url($vendor, $server, $username, $db)) . "'>($name) " . h("$username@" . ($server != "" ? adminer()->serverName($server) : "") . ($db != "" ? " - $db" : "")) . "</a>\n";
}
}
}
@@ -986,7 +1022,7 @@ class Adminer {
$actions[] = "<a href='" . h(ME) . "dump=" . urlencode(isset($_GET["table"]) ? $_GET["table"] : $_GET["select"]) . "' id='dump'" . bold(isset($_GET["dump"])) . ">" . lang('Export') . "</a>";
}
$in_db = $_GET["ns"] !== "" && !$missing && DB != "";
if ($in_db) {
if ($in_db && function_exists('Adminer\alter_table')) {
$actions[] = '<a href="' . h(ME) . 'create="' . bold($_GET["create"] === "") . ">" . lang('Create table') . "</a>";
}
echo ($actions ? "<p class='links'>\n" . implode("\n", $actions) . "\n" : "");
@@ -1018,17 +1054,27 @@ class Adminer {
foreach ($tables as $table => $type) {
$links[] = preg_quote($table, '/');
}
echo "var jushLinks = { " . JUSH . ": [ '" . js_escape(ME) . (support("table") ? "table=" : "select=") . "\$&', /\\b(" . implode("|", $links) . ")\\b/g ] };\n";
echo "var jushLinks = { " . JUSH . ":";
json_row(js_escape(ME) . (support("table") ? "table" : "select") . '=$&', '/\b(' . implode('|', $links) . ')\b/g', false);
if (support('routine')) {
foreach (routines() as $row) {
json_row(js_escape(ME) . 'function=' . urlencode($row["SPECIFIC_NAME"]) . '&name=$&', '/\b' . preg_quote($row["ROUTINE_NAME"], '/') . '(?=["`]?\()/g', false);
}
}
json_row('');
echo "};\n";
foreach (array("bac", "bra", "sqlite_quo", "mssql_bra") as $val) {
echo "jushLinks.$val = jushLinks." . JUSH . ";\n";
}
$tablesColumns = array_fill_keys(array_keys($tables), array());
foreach (driver()->allFields() as $table => $fields) {
foreach ($fields as $field) {
$tablesColumns[$table][] = $field["field"];
if (isset($_GET["sql"]) || isset($_GET["trigger"]) || isset($_GET["check"])) {
$tablesColumns = array_fill_keys(array_keys($tables), array());
foreach (driver()->allFields() as $table => $fields) {
foreach ($fields as $field) {
$tablesColumns[$table][] = $field["field"];
}
}
echo "addEventListener('DOMContentLoaded', () => { autocompleter = jush.autocompleteSql('" . idf_escape("") . "', " . json_encode($tablesColumns) . "); });\n";
}
echo "addEventListener('DOMContentLoaded', () => { autocompleter = jush.autocompleteSql('" . idf_escape("") . "', " . json_encode($tablesColumns) . "); });\n";
}
echo "</script>\n";
}
@@ -1072,8 +1118,9 @@ class Adminer {
function tablesPrint(array $tables): void {
echo "<ul id='tables'>" . script("mixin(qs('#tables'), {onmouseover: menuOver, onmouseout: menuOut});");
foreach ($tables as $table => $status) {
$table = "$table"; // do not highlight "0" as active everywhere
$name = adminer()->tableName($status);
if ($name != "") {
if ($name != "" && !$status["partition"]) {
echo '<li><a href="' . h(ME) . 'select=' . urlencode($table) . '"'
. bold($_GET["select"] == $table || $_GET["edit"] == $table, "select")
. " title='" . lang('Select data') . "'>" . lang('select') . "</a> "
@@ -1088,4 +1135,33 @@ class Adminer {
}
echo "</ul>\n";
}
/** Get server variables
* @return list<string[]> [[$name, $value]]
*/
function showVariables(): array {
return show_variables();
}
/** Get status variables
* @return list<string[]> [[$name, $value]]
*/
function showStatus(): array {
return show_status();
}
/** Get process list
* @return list<string[]> [$row]
*/
function processList(): array {
return process_list();
}
/** Kill a process
* @param numeric-string $id
* @return Result|bool
*/
function killProcess(string $id) {
return kill_process($id);
}
}

View File

@@ -172,7 +172,7 @@ if (isset($_GET["username"]) && !class_exists('Adminer\Db')) {
$connection = '';
if (isset($_GET["username"]) && is_string(get_password())) {
list($host, $port) = explode(":", SERVER, 2);
list(, $port) = host_port(SERVER);
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.'), $permanent);
}

View File

@@ -29,16 +29,6 @@ if (isset($_GET["file"])) {
include "../adminer/file.inc.php";
}
if ($_GET["script"] == "version") {
$filename = get_temp_dir() . "/adminer.version";
@unlink($filename); // it may not be writable by us, @ - it may not exist
$fp = file_open_lock($filename);
if ($fp) {
file_write_unlock($fp, serialize(array("signature" => $_POST["signature"], "version" => $_POST["version"])));
}
exit;
}
// Adminer doesn't use any global variables; they used to be declared here
if (!$_SERVER["REQUEST_URI"]) { // IIS 5 compatibility
@@ -61,7 +51,11 @@ if (!defined("SID")) {
}
// disable magic quotes to be able to use database escaping function
remove_slashes(array(&$_GET, &$_POST, &$_COOKIE), $filter);
if (function_exists("get_magic_quotes_gpc") && get_magic_quotes_gpc()) {
$_GET = remove_slashes($_GET, $filter);
$_POST = remove_slashes($_POST, $filter);
$_COOKIE = remove_slashes($_COOKIE, $filter);
}
if (function_exists("get_magic_quotes_runtime") && get_magic_quotes_runtime()) {
set_magic_quotes_runtime(false);
}
@@ -91,8 +85,8 @@ Adminer::$instance =
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, '' means default server, null means no server
define('Adminer\DB', $_GET["db"]); // for the sake of speed and size
define('Adminer\SERVER', "" . $_GET[DRIVER]); // read from pgsql=localhost, '' means default server
define('Adminer\DB', "$_GET[db]"); // for the sake of speed and size
define(
'Adminer\ME',
preg_replace('~\?.*~', '', relative_uri()) . '?'
@@ -108,3 +102,5 @@ include "../adminer/include/xxtea.inc.php";
include "../adminer/include/auth.inc.php";
include "./include/editing.inc.php";
include "./include/connect.inc.php";
adminer()->afterConnect();

View File

@@ -89,14 +89,26 @@ if (
echo script("tableCheck();");
}
if (isset(adminer()->plugins) && is_array(adminer()->plugins)) {
if (!empty(adminer()->plugins)) {
echo "<div class='plugins'>\n";
echo "<h3>" . lang('Loaded plugins') . "</h3>\n<ul>\n";
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";
$description = (method_exists($plugin, 'description') ? $plugin->description() : "");
if (!$description) {
$reflection = new \ReflectionObject($plugin);
if (preg_match('~^/[\s*]+(.+)~', $reflection->getDocComment(), $match)) {
$description = $match[1];
}
}
$screenshot = (method_exists($plugin, 'screenshot') ? $plugin->screenshot() : "");
echo "<li><b>" . get_class($plugin) . "</b>"
. h($description ? ": $description" : "")
. ($screenshot ? " (<a href='" . h($screenshot) . "'" . target_blank() . ">" . lang('screenshot') . "</a>)" : "")
. "\n"
;
}
echo "</ul>\n";
adminer()->pluginsLinks();
echo "</div>\n";
}
}

View File

@@ -18,7 +18,7 @@ abstract class SqlDb {
/** Connect to server
* @return string error message
*/
abstract function attach(?string $server, string $username, string $password): string;
abstract function attach(string $server, string $username, string $password): string;
/** Quote string to use in SQL
* @return string escaped string enclosed in '

View File

@@ -27,17 +27,13 @@ function page_header(string $title, string $error = "", $breadcrumb = array(), s
<title><?php echo $title_page; ?></title>
<link rel="stylesheet" href="../adminer/static/default.css">
<?php
$css = adminer()->css();
$has_light = false;
$has_dark = false;
foreach ($css as $filename) {
if (strpos($filename, "adminer.css") !== false) {
$has_light = true;
}
if (strpos($filename, "adminer-dark.css") !== false) {
$has_dark = true;
}
if (is_int(key($css))) { // legacy return value
$css = array_fill_keys($css, 'light');
}
$has_light = in_array('light', $css) || in_array('', $css);
$has_dark = in_array('dark', $css) || in_array('', $css);
$dark = ($has_light
? ($has_dark ? null : false) // both styles - autoswitching, only adminer.css - light
: ($has_dark ?: null) // only adminer-dark.css - dark, neither - autoswitching
@@ -47,6 +43,7 @@ function page_header(string $title, string $error = "", $breadcrumb = array(), s
echo "<link rel='stylesheet'" . ($dark ? "" : $media) . " href='../adminer/static/dark.css'>\n";
}
echo "<meta name='color-scheme' content='" . ($dark === null ? "light dark" : ($dark ? "dark" : "light")) . "'>\n";
// this is matched by compile.php
echo script_src("../adminer/static/functions.js");
echo script_src("static/editing.js");
@@ -54,29 +51,19 @@ function page_header(string $title, string $error = "", $breadcrumb = array(), s
echo "<link rel='icon' href='data:image/gif;base64,R0lGODlhEAAQAJEAAAQCBPz+/PwCBAROZCH5BAEAAAAALAAAAAAQABAAAAI2hI+pGO1rmghihiUdvUBnZ3XBQA7f05mOak1RWXrNq5nQWHMKvuoJ37BhVEEfYxQzHjWQ5qIAADs='>\n";
echo "<link rel='apple-touch-icon' href='../adminer/static/logo.png'>\n";
}
foreach ($css as $val) {
echo "<link rel='stylesheet'" . (preg_match('~-dark\.~', $val) && !$dark ? $media : "") . " href='" . h($val) . "'>\n";
foreach ($css as $url => $mode) {
$attrs = ($mode == 'dark' && !$dark
? $media
: ($mode == 'light' && $has_dark ? " media='(prefers-color-scheme: light)'" : "")
);
echo "<link rel='stylesheet'$attrs href='" . h($url) . "'>\n";
}
echo "\n<body class='" . lang('ltr') . " nojs'>\n";
echo "\n<body class='" . lang('ltr') . " nojs";
adminer()->bodyClass();
echo "'>\n";
$filename = get_temp_dir() . "/adminer.version";
if (!$_COOKIE["adminer_version"] && function_exists('openssl_verify') && file_exists($filename) && filemtime($filename) + 86400 > time()) { // 86400 - 1 day in seconds
$version = unserialize(file_get_contents($filename));
$public = "-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwqWOVuF5uw7/+Z70djoK
RlHIZFZPO0uYRezq90+7Amk+FDNd7KkL5eDve+vHRJBLAszF/7XKXe11xwliIsFs
DFWQlsABVZB3oisKCBEuI71J4kPH8dKGEWR9jDHFw3cWmoH3PmqImX6FISWbG3B8
h7FIx3jEaw5ckVPVTeo5JRm/1DZzJxjyDenXvBQ/6o9DgZKeNDgxwKzH+sw9/YCO
jHnq1cFpOIISzARlrHMa/43YfeNRAm/tsBXjSxembBPo7aQZLAWHmaj5+K19H10B
nCpz9Y++cipkVEiKRGih4ZEvjoFysEOdRLj6WiD/uUNky4xGeA6LaJqh5XpkFkcQ
fQIDAQAB
-----END PUBLIC KEY-----
";
if (openssl_verify($version["version"], base64_decode($version["signature"]), $public) == 1) {
$_COOKIE["adminer_version"] = $version["version"]; // doesn't need to send to the browser
}
}
echo script("mixin(document.body, {onkeydown: bodyKeydown, onclick: bodyClick"
. (isset($_COOKIE["adminer_version"]) ? "" : ", onload: partial(verifyVersion, '" . VERSION . "', '" . js_escape(ME) . "', '" . get_token() . "')")
. (isset($_COOKIE["adminer_version"]) ? "" : ", onload: partial(verifyVersion, '" . VERSION . "')")
. "});
document.body.classList.replace('nojs', 'js');
const offlineMessage = '" . js_escape(lang('You are offline.')) . "';
@@ -150,8 +137,8 @@ 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-'
"connect-src" => "'self'",
"frame-src" => "https://www.adminer.org",
"connect-src" => "'self' https://www.adminer.org",
"frame-src" => "'none'",
"object-src" => "'none'",
"base-uri" => "'none'",
"form-action" => "'self'",

View File

@@ -7,7 +7,7 @@ function add_driver(string $id, string $name): void {
}
/** Get driver name */
function get_driver(string $id): string {
function get_driver(string $id): ?string {
return SqlDriver::$drivers[$id];
}
@@ -19,6 +19,7 @@ abstract class SqlDriver {
/** @var Db */ protected $conn;
/** @var int[][] */ protected $types = array(); // [$group => [$type => $maximum_unsigned_length, ...], ...]
/** @var string */ public $delimiter = ";";
/** @var string[] */ public $insertFunctions = array(); // ["$type|$type2" => "$function/$function2"] functions used in edit and insert
/** @var string[] */ public $editFunctions = array(); // ["$type|$type2" => "$function/$function2"] functions used in edit only
/** @var list<string> */ public $unsigned = array(); // number variants
@@ -26,6 +27,7 @@ abstract class SqlDriver {
/** @var list<string> */ public $functions = array(); // functions used in select
/** @var list<string> */ public $grouping = array(); // grouping functions used in select
/** @var string */ public $onActions = "RESTRICT|NO ACTION|CASCADE|SET NULL|SET DEFAULT"; // used in foreign_keys()
/** @var list<string> */ public $partitionBy = array(); // supported partitioning types
/** @var string */ public $inout = "IN|OUT|INOUT"; // used in routines
/** @var string */ public $enumLength = "'(?:''|[^'\\\\]|\\\\.)*'"; // regular expression for parsing enum lengths
/** @var list<string> */ public $generated = array(); // allowed types of generated columns
@@ -33,7 +35,7 @@ abstract class SqlDriver {
/** Connect to the database
* @return Db|string string for error
*/
static function connect(?string $server, string $username, string $password) {
static function connect(string $server, string $username, string $password) {
$connection = new Db;
return ($connection->attach($server, $username, $password) ?: $connection);
}
@@ -188,19 +190,11 @@ abstract class SqlDriver {
return $idf;
}
/** 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 Field $field
* @param array{type: string} $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)
);
return (method_exists($this->conn, 'value') ? $this->conn->value($val, $field) : $val);
}
/** Quote binary string */
@@ -220,6 +214,27 @@ abstract class SqlDriver {
function tableHelp(string $name, bool $is_view = false) {
}
/** Get tables this table inherits from
* @return list<array{table: string, ns: string}>
*/
function inheritsFrom(string $table): array {
return array();
}
/** Get inherited tables
* @return list<array{table: string, ns: string}>
*/
function inheritedTables(string $table): array {
return array();
}
/** Get partitions info
* @return Partitions
*/
function partitionsInfo(string $table): array {
return array();
}
/** Check if C-style escapes are supported */
function hasCStyleEscapes(): bool {
return false;
@@ -239,6 +254,14 @@ abstract class SqlDriver {
return !is_view($table_status);
}
/** Return list of supported index algorithms, first one is default
* @param TableStatus $tableStatus
* @return list<string>
*/
function indexAlgorithms(array $tableStatus): array {
return array();
}
/** Get defined check constraints
* @return string[] [$name => $clause]
*/
@@ -246,23 +269,27 @@ abstract class SqlDriver {
// 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
JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS t ON c.CONSTRAINT_SCHEMA = t.CONSTRAINT_SCHEMA AND c.CONSTRAINT_NAME = t.CONSTRAINT_NAME" . ($this->conn->flavor == 'maria' ? " AND c.TABLE_NAME = t.TABLE_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'", $this->conn); // ignore default IS NOT NULL checks in PostrgreSQL
AND t.TABLE_NAME = " . q($table) . (JUSH == "pgsql" ? "
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}>>
* @return array<list<array{field:string, null:bool, type:string, length:?numeric-string}>> optionally also 'primary'
*/
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`" : "") . "
if (DB != "") {
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;
ORDER BY TABLE_NAME, ORDINAL_POSITION", $this->conn) as $row
) {
$row["null"] = ($row["nullable"] == "YES");
$return[$row["tab"]][] = $row;
}
}
return $return;
}

View File

@@ -72,25 +72,18 @@ function print_select_result($result, ?Db $connection2 = null, array $orgtables
} else {
$link = ME . "edit=" . urlencode($links[$key]);
foreach ($indexes[$links[$key]] as $col => $j) {
if ($row[$j] === null) {
$link = "";
break;
}
$link .= "&where" . urlencode("[" . bracket_escape($col) . "]") . "=" . urlencode($row[$j]);
}
}
} elseif (is_url($val)) {
$link = $val;
}
if ($val === null) {
$val = "<i>NULL</i>";
} elseif ($blobs[$key] && !is_utf8($val)) {
$val = "<i>" . lang('%d byte(s)', strlen($val)) . "</i>"; //! link to download
} else {
$val = h($val);
if ($types[$key] == 254) { // 254 - char
$val = "<code>$val</code>";
}
}
if ($link) {
$val = "<a href='" . h($link) . "'" . (is_url($link) ? target_blank() : '') . ">$val</a>";
}
$field = array(
'type' => ($blobs[$key] ? 'blob' : ($types[$key] == 254 ? 'char' : '')),
);
$val = select_value($val, $link, $field, null);
// https://dev.mysql.com/doc/dev/mysql-server/latest/field__types_8h.html
echo "<td" . ($types[$key] <= 9 || $types[$key] == 246 ? " class='number'" : "") . ">$val";
}
@@ -150,13 +143,13 @@ function select_input(string $attrs, array $options, ?string $value = "", string
* @param string $key or "" to close the object
* @param string|int $val
*/
function json_row(string $key, $val = null): void {
function json_row(string $key, $val = null, bool $escape = true): void {
static $first = true;
if ($first) {
echo "{";
}
if ($key != "") {
echo ($first ? "" : ",") . "\n\t\"" . addcslashes($key, "\r\n\t\"\\/") . '": ' . ($val !== null ? '"' . addcslashes($val, "\r\n\"\\/") . '"' : 'null');
echo ($first ? "" : ",") . "\n\t\"" . addcslashes($key, "\r\n\t\"\\/") . '": ' . ($val !== null ? ($escape ? '"' . addcslashes($val, "\r\n\"\\/") . '"' : $val) : 'null');
$first = false;
} else {
echo "\n}\n";
@@ -201,20 +194,6 @@ function edit_type(string $key, array $field, array $collations, array $foreign_
);
}
/** Get partition info
* @return array{partition_by:string, partition:string, partitions:string, partition_names:list<string>, partition_values:list<string>}
*/
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");
$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");
$return["partition_names"] = array_keys($partitions);
$return["partition_values"] = array_values($partitions);
return $return;
}
/** Filter length value including enums */
function process_length(?string $length): string {
$enum_length = driver()->enumLength;
@@ -260,15 +239,18 @@ function process_field(array $field, array $type_field): array {
* @param Field $field
*/
function default_value(array $field): string {
$default = $field["default"];
if ($field["default"] === null) {
return "";
}
$default = str_replace("\r", "", $field["default"]);
$generated = $field["generated"];
return ($default === null ? "" : (in_array($generated, driver()->generated)
? (JUSH == "mssql" ? " AS ($default)" . ($generated == "VIRTUAL" ? "" : " $generated") . "" : " GENERATED ALWAYS AS ($default) $generated")
return (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
: str_ireplace("current_timestamp()", "CURRENT_TIMESTAMP", (JUSH == "sqlite" ? "($default)" : $default))
)
));
);
}
/** Get type class to use in CSS
@@ -328,7 +310,7 @@ function edit_fields(array $fields, array $collations, $type = "TABLE", array $f
echo "<tr" . ($display ? "" : " style='display: none;'") . ">\n";
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'>";
echo "<input name='fields[$i][field]' value='" . h($field["field"]) . "' data-maxlength='64' autocapitalize='off' aria-labelledby='label-name'" . (isset($_POST["add"][$i-1]) ? " autofocus" : "") . ">";
}
echo input_hidden("fields[$i][orig]", $orig);
edit_type("fields[$i]", $field, $collations, $foreign_keys);
@@ -339,7 +321,9 @@ function edit_fields(array $fields, array $collations, $type = "TABLE", array $f
? 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'>";
$attrs = " name='fields[$i][default]' aria-labelledby='label-default'";
$value = h($field["default"]);
echo (preg_match('~\n~', $field["default"]) ? "<textarea$attrs rows='2' cols='30' style='vertical-align: bottom;'>\n$value</textarea>" : "<input$attrs value='$value'>"); // \n to preserve the leading newline
echo (support("comment") ? "<td$comment_class><input name='fields[$i][comment]' value='" . h($field["comment"]) . "' data-maxlength='" . (min_version(5.5) ? 1024 : 255) . "' aria-labelledby='label-comment'>" : "");
}
echo "<td>";
@@ -470,7 +454,7 @@ function create_routine($routine, array $row): string {
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"], ";");
@@ -499,8 +483,9 @@ function format_foreign_key(array $foreign_key): string {
. ($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]" : "")
. ($foreign_key["deferrable"] ? " $foreign_key[deferrable]" : "")
;
}
@@ -521,20 +506,6 @@ function tar_file(string $filename, $tmp_file): void {
echo str_repeat("\0", 511 - ($tmp_file->size + 511) % 512);
}
/** Get INI bytes value */
function ini_bytes(string $ini): int {
$val = ini_get($ini);
switch (strtolower(substr($val, -1))) {
case 'g':
$val = (int) $val * 1024; // no break
case 'm':
$val = (int) $val * 1024; // no break
case 'k':
$val = (int) $val * 1024;
}
return $val;
}
/** Create link to database documentation
* @param string[] $paths JUSH => $path
* @param string $text HTML code

View File

@@ -76,24 +76,19 @@ function number_type(): string {
}
/** Disable magic_quotes_gpc
* @param list<array> $process e.g. [&$_GET, &$_POST, &$_COOKIE]
* @param mixed[] $values
* @param bool $filter whether to leave values as is
* @return void modified in place
* @return mixed[]
*/
function remove_slashes(array $process, bool $filter = false): void {
if (function_exists("get_magic_quotes_gpc") && get_magic_quotes_gpc()) {
while (list($key, $val) = each($process)) {
foreach ($val as $k => $v) {
unset($process[$key][$k]);
if (is_array($v)) {
$process[$key][stripslashes($k)] = $v;
$process[] = &$process[$key][stripslashes($k)];
} else {
$process[$key][stripslashes($k)] = ($filter ? $v : stripslashes($v));
}
}
}
function remove_slashes(array $values, bool $filter = false): array {
$return = array();
foreach ($values as $key => $val) {
$return[stripslashes($key)] = (is_array($val)
? remove_slashes($val, $filter)
: ($filter ? $val : stripslashes($val))
);
}
return $return;
}
/** Escape or unescape string to use inside form [] */
@@ -128,6 +123,20 @@ function ini_bool(string $ini): bool {
return (preg_match('~^(on|true|yes)$~i', $val) || (int) $val); // boolean values set by php_value are strings
}
/** Get INI bytes value */
function ini_bytes(string $ini): int {
$val = ini_get($ini);
switch (strtolower(substr($val, -1))) {
case 'g':
$val = (int) $val * 1024; // no break
case 'm':
$val = (int) $val * 1024; // no break
case 'k':
$val = (int) $val * 1024;
}
return $val;
}
/** Check if SID is necessary */
function sid(): bool {
static $return;
@@ -230,7 +239,7 @@ function get_rows(string $query, ?Db $connection2 = null, string $error = "<p cl
*/
function unique_array(?array $row, array $indexes) {
foreach ($indexes as $index) {
if (preg_match("~PRIMARY|UNIQUE~", $index["type"])) {
if (preg_match("~PRIMARY|UNIQUE~", $index["type"]) && !$index["partial"]) {
$return = array();
foreach ($index["columns"] as $key) {
if (!isset($row[$key])) { // NULL is ambiguous
@@ -264,9 +273,10 @@ function where(array $where, array $fields = array()): string {
$field_type = $field["type"];
$return[] = $column
. (JUSH == "sql" && $field_type == "json" ? " = CAST(" . q($val) . " AS JSON)"
: (JUSH == "pgsql" && preg_match('~^json~', $field_type) ? "::jsonb = " . q($val) . "::jsonb"
: (JUSH == "sql" && is_numeric($val) && preg_match('~\.~', $val) ? " LIKE " . q($val) // LIKE because of floats but slow with ints
: (JUSH == "mssql" && strpos($field_type, "datetime") === false ? " LIKE " . q(preg_replace('~[_%[]~', '[\0]', $val)) // LIKE because of text but it does not work with datetime
: " = " . unconvert_field($field, q($val)))))
: " = " . unconvert_field($field, q($val))))))
; //! enum and set
if (JUSH == "sql" && preg_match('~char|text~', $field_type) && preg_match("~[^ -@]~", $val)) { // not just [a-z] to catch non-ASCII characters
$return[] = "$column = " . q($val) . " COLLATE " . charset(connection()) . "_bin";
@@ -291,7 +301,7 @@ function where_check(string $val, array $fields = array()): string {
* @param int $i condition order
* @param string $column column identifier
*/
function where_link(int $i, string $column, string $value, string $operator = "="): string {
function where_link(int $i, string $column, ?string $value, string $operator = "="): string {
return "&where%5B$i%5D%5Bcol%5D=" . urlencode($column) . "&where%5B$i%5D%5Bop%5D=" . urlencode(($value !== null ? $operator : "IS NULL")) . "&where%5B$i%5D%5Bval%5D=" . urlencode($value);
}
@@ -319,7 +329,7 @@ function convert_fields(array $columns, array $fields, array $select = array()):
*/
function cookie(string $name, ?string $value, int $lifetime = 2592000): void {
header(
"Set-Cookie: $name=" . urlencode($value)
"Set-Cookie: $name=" . rawurlencode($value)
. ($lifetime ? "; expires=" . gmdate("D, d M Y H:i:s", time() + $lifetime) . " GMT" : "")
. "; path=" . preg_replace('~\?.*~', '', $_SERVER["REQUEST_URI"])
. (HTTPS ? "; secure" : "")
@@ -337,11 +347,11 @@ function get_settings(string $cookie): array {
}
/** Get setting stored in a cookie
* @param mixed $default
* @return mixed
*/
function get_setting(string $key, string $cookie = "adminer_settings") {
$settings = get_settings($cookie);
return $settings[$key];
function get_setting(string $key, string $cookie = "adminer_settings", $default = null) {
return idx(get_settings($cookie), $key, $default);
}
/** Store settings to a cookie
@@ -460,7 +470,7 @@ function queries(string $query) {
if (!Queries::$start) {
Queries::$start = microtime(true);
}
Queries::$queries[] = (preg_match('~;$~', $query) ? "DELIMITER ;;\n$query;\nDELIMITER " : $query) . ";";
Queries::$queries[] = (driver()->delimiter != ';' ? $query : (preg_match('~;$~', $query) ? "DELIMITER ;;\n$query;\nDELIMITER " : $query) . ";");
return connection()->query($query);
}
@@ -595,10 +605,10 @@ function column_foreign_keys(string $table): array {
return $return;
}
/** Compute fields() from $_POST edit data
/** Compute fields() from $_POST edit data; used by Mongo and SimpleDB
* @return Field[] same as fields()
*/
function fields_from_edit(): array { // used by Mongo and SimpleDB
function fields_from_edit(): array {
$return = array();
foreach ((array) $_POST["field_keys"] as $key => $val) {
if ($val != "") {
@@ -641,12 +651,13 @@ function dump_headers(string $identifier, bool $multi_table = false): string {
* @param string[] $row
*/
function dump_csv(array $row): void {
$tsv = $_POST["format"] == "tsv";
foreach ($row as $key => $val) {
if (preg_match('~["\n,;\t]|^0|\.\d*0$~', $val) || $val === "") {
if (preg_match('~["\n]|^0[^.]|\.\d*0$|' . ($tsv ? '\t' : '[,;]|^$') . '~', $val)) {
$row[$key] = '"' . str_replace('"', '""', $val) . '"';
}
}
echo implode(($_POST["format"] == "csv" ? "," : ($_POST["format"] == "tsv" ? "\t" : ";")), $row) . "\r\n";
echo implode(($_POST["format"] == "csv" ? "," : ($tsv ? "\t" : ";")), $row) . "\r\n";
}
/** Apply SQL function
@@ -685,7 +696,7 @@ function file_open_lock(string $filename) {
if (!$fp) {
return;
}
chmod($filename, 0660);
@chmod($filename, 0660); // @ - may not be permitted
if (!flock($fp, LOCK_EX)) {
fclose($fp);
return;
@@ -750,19 +761,35 @@ function rand_string(): string {
}
/** Format value to use in select
* @param string|string[] $val
* @param Field $field
* @param string|string[]|list<string[]> $val
* @param array{type: string} $field
* @param ?numeric-string $text_length
* @return string HTML
*/
function select_value($val, string $link, array $field, ?string $text_length): string {
if (is_array($val)) {
$return = "";
foreach ($val as $k => $v) {
$return .= "<tr>"
. ($val != array_values($val) ? "<th>" . h($k) : "")
. "<td>" . select_value($v, $link, $field, $text_length)
;
if (array_filter($val, 'is_array') == array_values($val)) { // list of arrays
$keys = array();
foreach ($val as $v) {
$keys += array_fill_keys(array_keys($v), null);
}
foreach (array_keys($keys) as $k) {
$return .= "<th>" . h($k);
}
foreach ($val as $v) {
$return .= "<tr>";
foreach (array_merge($keys, $v) as $v2) {
$return .= "<td>" . select_value($v2, $link, $field, $text_length);
}
}
} else {
foreach ($val as $k => $v) {
$return .= "<tr>"
. ($val != array_values($val) ? "<th>" . h($k) : "")
. "<td>" . select_value($v, $link, $field, $text_length)
;
}
}
return "<table>$return</table>";
}
@@ -777,7 +804,7 @@ function select_value($val, string $link, array $field, ?string $text_length): s
$link = $val; // IE 11 and all modern browsers hide referrer
}
}
$return = adminer()->editVal($val, $field);
$return = adminer()->editVal(driver()->value($val, $field), $field);
if ($return !== null) {
if (!is_utf8($return)) {
$return = "\0"; // htmlspecialchars of binary data returns an empty string
@@ -790,6 +817,13 @@ function select_value($val, string $link, array $field, ?string $text_length): s
return adminer()->selectVal($return, $link, $field, $val);
}
/** Check whether the field type is blob or equivalent
* @param array{type: string} $field
*/
function is_blob(array $field): bool {
return preg_match('~blob|bytea|raw|file~', $field["type"]) && !in_array($field["type"], idx(driver()->structuredTypes(), lang('User types'), array()));
}
/** Check whether the string is e-mail address */
function is_mail(?string $email): bool {
$atom = '[-a-z0-9!#$%&\'*+/=?^_`{|}~]'; // characters of local-name
@@ -801,14 +835,24 @@ function is_mail(?string $email): bool {
/** Check whether the string is URL address */
function is_url(?string $string): bool {
$domain = '[a-z0-9]([-a-z0-9]{0,61}[a-z0-9])'; // one domain component //! IDN
return preg_match("~^(https?)://($domain?\\.)+$domain(:\\d+)?(/.*)?(\\?.*)?(#.*)?\$~i", $string); //! restrict path, query and fragment characters
return preg_match("~^((https?):)?//($domain?\\.)+$domain(:\\d+)?(/.*)?(\\?.*)?(#.*)?\$~i", $string); //! restrict path, query and fragment characters
}
/** Check if field should be shortened
* @param Field $field
* @param array{type: string} $field
*/
function is_shortable(array $field): bool {
return preg_match('~char|text|json|lob|geometry|point|linestring|polygon|string|bytea~', $field["type"]);
return !preg_match('~' . number_type() . '|date|time|year~', $field["type"]);
}
/** Split server into host and (port or socket)
* @return array{0: string, 1: string}
*/
function host_port(string $server) {
return (preg_match('~^(\[(.+)]|([^:]+)):([^:]+)$~', $server, $match) // [a:b] - IPv6
? array($match[2] . $match[3], $match[4])
: array($server, '')
);
}
/** Get query to compute number of found rows

View File

@@ -176,18 +176,34 @@ function hidden_fields_get(): void {
echo input_hidden("username", $_GET["username"]);
}
/** Get <input type='file'> */
function file_input(string $input): string {
$max_file_uploads = "max_file_uploads";
$max_file_uploads_value = ini_get($max_file_uploads);
$upload_max_filesize = "upload_max_filesize";
$upload_max_filesize_value = ini_get($upload_max_filesize);
return (ini_bool("file_uploads")
? $input . script("qsl('input[type=\"file\"]').onchange = partialArg(fileChange, "
. "$max_file_uploads_value, '" . lang('Increase %s.', "$max_file_uploads = $max_file_uploads_value") . "', " // ignore post_max_size because it is for all form fields together and bytes computing would be necessary
. ini_bytes("upload_max_filesize") . ", '" . lang('Increase %s.', "$upload_max_filesize = $upload_max_filesize_value") . "')")
: lang('File uploads are disabled.')
);
}
/** Print enum or set input field
* @param 'radio'|'checkbox' $type
* @param Field $field
* @param mixed $value string|array
* @param string|string[]|false|null $value false means original value
*/
function enum_input(string $type, string $attrs, array $field, $value, ?string $empty = null): string {
function enum_input(string $type, string $attrs, array $field, $value, string $empty = ""): 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) {
$prefix = ($field["type"] == "enum" ? "val-" : "");
$checked = (is_array($value) ? in_array("null", $value) : $value === null);
$return = ($field["null"] && $prefix ? "<label><input type='$type'$attrs value='null'" . ($checked ? " checked" : "") . "><i>$empty</i></label>" : "");
foreach ($matches[1] as $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>';
$checked = (is_array($value) ? in_array($prefix . $val, $value) : $value === $val);
$return .= " <label><input type='$type'$attrs value='" . h($prefix . $val) . "'" . ($checked ? ' checked' : '') . '>' . h(adminer()->editVal($val, $field)) . '</label>';
}
return $return;
}
@@ -200,21 +216,23 @@ function input(array $field, $value, ?string $function, ?bool $autofocus = false
$name = h(bracket_escape($field["field"]));
echo "<td class='function'>";
if (is_array($value) && !$function) {
$value = json_encode($value, 128 | 64 | 256); // 128 - JSON_PRETTY_PRINT, 64 - JSON_UNESCAPED_SLASHES, 256 - JSON_UNESCAPED_UNICODE available since PHP 5.4
$function = "json";
}
$json = ($function == "json" || preg_match('~^jsonb?$~', $field["type"]));
if ($json && $value != '' && (JUSH != "pgsql" || $field["type"] != "json")) {
$value = json_encode(is_array($value) ? $value : json_decode($value), 128 | 64 | 256); // 128 - JSON_PRETTY_PRINT, 64 - JSON_UNESCAPED_SLASHES, 256 - JSON_UNESCAPED_UNICODE available since PHP 5.4
}
$reset = (JUSH == "mssql" && $field["auto_increment"]);
if ($reset && !$_POST["save"]) {
$function = null;
}
$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);
if ($enums) {
$field["type"] = "enum";
$field["length"] = $enums;
}
$attrs = " name='fields[$name]" . ($field["type"] == "enum" || $field["type"] == "set" ? "[]" : "") . "'" . ($autofocus ? " autofocus" : "");
echo driver()->unconvertFunction($field) . " ";
$table = $_GET["edit"] ?: $_GET["select"];
if ($field["type"] == "enum") {
@@ -222,7 +240,7 @@ function input(array $field, $value, ?string $function, ?bool $autofocus = false
} else {
$has_function = (in_array($function, $functions) || isset($functions[$function]));
echo (count($functions) > 1
? "<select name='function[$name]'$disabled>" . optionlist($functions, $function === null || $has_function ? $function : "") . "</select>"
? "<select name='function[$name]'>" . optionlist($functions, $function === null || $has_function ? $function : "") . "</select>"
. on_help("event.target.value.replace(/^SQL\$/, '')", 1)
. script("qsl('select').onchange = functionChange;", "")
: h(reset($functions))
@@ -234,15 +252,10 @@ function input(array $field, $value, ?string $function, ?bool $autofocus = false
echo "<input type='hidden'$attrs value='0'>"
. "<input type='checkbox'" . (preg_match('~^(1|t|true|y|yes|on)$~i', $value) ? " checked='checked'" : "") . "$attrs value='1'>";
} elseif ($field["type"] == "set") {
preg_match_all("~'((?:[^']|'')*)'~", $field["length"], $matches);
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>';
}
} elseif (preg_match('~blob|bytea|raw|file~', $field["type"]) && ini_bool("file_uploads")) {
echo enum_input("checkbox", $attrs, $field, (is_string($value) ? explode(",", $value) : $value));
} elseif (is_blob($field) && ini_bool("file_uploads")) {
echo "<input type='file' name='fields-$name'>";
} elseif ($function == "json" || preg_match('~^jsonb?$~', $field["type"])) {
} elseif ($json) {
echo "<textarea$attrs cols='50' rows='12' class='jush-js'>" . h($value) . '</textarea>';
} elseif (($text = preg_match('~text|lob|memo~i', $field["type"])) || preg_match("~\n~", $value)) {
if ($text && JUSH != "sqlite") {
@@ -290,19 +303,21 @@ function input(array $field, $value, ?string $function, ?bool $autofocus = false
* @return mixed false to leave the original value
*/
function process_input(array $field) {
if (stripos($field["default"], "GENERATED ALWAYS AS ") === 0) {
return;
}
$idf = bracket_escape($field["field"]);
$function = idx($_POST["function"], $idf);
$value = $_POST["fields"][$idf];
$value = idx($_POST["fields"], $idf);
if ($value === null) {
return false;
}
if ($field["type"] == "enum" || driver()->enumLength($field)) {
if ($value == -1) {
$value = idx($value, 0);
if ($value == "orig" || !$value) {
return false;
}
if ($value == "") {
if ($value == "null") {
return "NULL";
}
$value = substr($value, 4); // 4 - strlen("val-")
}
if ($field["auto_increment"] && $value == "") {
return null;
@@ -324,7 +339,7 @@ function process_input(array $field) {
}
return $value;
}
if (preg_match('~blob|bytea|raw|file~', $field["type"]) && ini_bool("file_uploads")) {
if (is_blob($field) && ini_bool("file_uploads")) {
$file = get_file("fields-$idf");
if (!is_string($file)) {
return false; //! report errors
@@ -381,6 +396,7 @@ function edit_form(string $table, array $fields, $row, ?bool $update, string $er
return;
}
echo "<form action='' method='post' enctype='multipart/form-data' id='form'>\n";
$editable = false;
if (!$fields) {
echo "<p class='error'>" . lang('You have no privileges to update this table.') . "\n";
} else {
@@ -411,30 +427,35 @@ function edit_form(string $table, array $fields, $row, ?bool $update, string $er
if (!$_POST["save"] && is_string($value)) {
$value = adminer()->editVal($value, $field);
}
$function = ($_POST["save"]
? idx($_POST["function"], $name, "")
: ($update && preg_match('~^CURRENT_TIMESTAMP~i', $field["on_update"])
? "now"
: ($value === false ? null : ($value !== null ? '' : 'NULL'))
)
);
if (!$_POST && !$update && $value == $field["default"] && preg_match('~^[\w.]+\(~', $value)) {
$function = "SQL";
}
if (preg_match("~time~", $field["type"]) && preg_match('~^CURRENT_TIMESTAMP~i', $value)) {
$value = "";
$function = "now";
}
if ($field["type"] == "uuid" && $value == "uuid()") {
$value = "";
$function = "uuid";
}
if ($autofocus !== false) {
$autofocus = ($field["auto_increment"] || $function == "now" || $function == "uuid" ? null : true); // null - don't autofocus this input but check the next one
}
input($field, $value, $function, $autofocus);
if ($autofocus) {
$autofocus = false;
if (($update && !isset($field["privileges"]["update"])) || $field["generated"]) {
echo "<td class='function'><td>" . select_value($value, '', $field, null);
} else {
$editable = true;
$function = ($_POST["save"]
? idx($_POST["function"], $name, "")
: ($update && preg_match('~^CURRENT_TIMESTAMP~i', $field["on_update"])
? "now"
: ($value === false ? null : ($value !== null ? '' : 'NULL'))
)
);
if (!$_POST && !$update && $value == $field["default"] && preg_match('~^[\w.]+\(~', $value)) {
$function = "SQL";
}
if (preg_match("~time~", $field["type"]) && preg_match('~^CURRENT_TIMESTAMP~i', $value)) {
$value = "";
$function = "now";
}
if ($field["type"] == "uuid" && $value == "uuid()") {
$value = "";
$function = "uuid";
}
if ($autofocus !== false) {
$autofocus = ($field["auto_increment"] || $function == "now" || $function == "uuid" ? null : true); // null - don't autofocus this input but check the next one
}
input($field, $value, $function, $autofocus);
if ($autofocus) {
$autofocus = false;
}
}
echo "\n";
}
@@ -450,7 +471,7 @@ function edit_form(string $table, array $fields, $row, ?bool $update, string $er
echo "</table>\n";
}
echo "<p>\n";
if ($fields) {
if ($editable) {
echo "<input type='submit' value='" . lang('Save') . "'>\n";
if (!isset($_GET["select"])) {
echo "<input type='submit' name='insert' value='" . ($update

View File

@@ -66,6 +66,7 @@ function langs(): array {
'fr' => 'Français', // Francis Gagné, Aurélien Royer
'gl' => 'Galego', // Eduardo Penabad Ramos
'he' => 'עברית', // Binyamin Yawitz - https://stuff-group.com/
'hi' => 'हिन्दी', // Joshi yogesh
'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

View File

@@ -71,11 +71,20 @@ if (extension_loaded('pdo')) {
public $_offset = 0, $num_rows;
function fetch_assoc() {
return $this->fetch(\PDO::FETCH_ASSOC);
return $this->fetch_array(\PDO::FETCH_ASSOC);
}
function fetch_row() {
return $this->fetch(\PDO::FETCH_NUM);
return $this->fetch_array(\PDO::FETCH_NUM);
}
private function fetch_array(int $mode) {
$return = $this->fetch($mode);
return ($return ? array_map(array($this, 'unresource'), $return) : $return);
}
private function unresource($val) {
return (is_resource($val) ? stream_get_contents($val) : $val);
}
function fetch_field(): \stdClass {

View File

@@ -1,16 +1,31 @@
<?php
namespace Adminer;
// the overridable methods don't use return type declarations so that plugins can be compatible with PHP 5
abstract class Plugin {
/** @var array<literal-string, string|list<string>>[] */ protected static $translations = array(); // key is language code
/** @var array<literal-string, string|list<string>>[] */ protected $translations = array(); // key is language code
/** Translate a string from static::$translations; use Adminer\lang() for strings used by Adminer
/** Get plain text plugin description; empty string means to use the first line of class doc-comment
* @return string
*/
function description() {
return $this->lang('');
}
/** Get URL of plugin screenshot
* @return string
*/
function screenshot() {
return "";
}
/** Translate a string from $this->translations; Adminer\lang() doesn't work for single language versions
* @param literal-string $idf
* @param float|string $number
*/
protected function lang(string $idf, $number = null) {
protected function lang(string $idf, $number = null): string {
$args = func_get_args();
$args[0] = idx(static::$translations[LANG], $idf) ?: $idf;
$args[0] = idx($this->translations[LANG], $idf) ?: $idf;
return call_user_func_array('Adminer\lang_format', $args);
}
}

View File

@@ -17,12 +17,12 @@ class Plugins {
$basename = "adminer-plugins";
if (is_dir($basename)) {
foreach (glob("$basename/*.php") as $filename) {
$include = include_once "./$filename";
$this->includeOnce($filename);
}
}
$help = " href='https://www.adminer.org/plugins/#use'" . target_blank();
if (file_exists("$basename.php")) {
$include = include_once "./$basename.php"; // example: return array(new AdminerLoginOtp($secret))
$include = $this->includeOnce("$basename.php"); // example: return array(new AdminerLoginOtp($secret));
if (is_array($include)) {
foreach ($include as $plugin) {
$plugins[get_class($plugin)] = $plugin;
@@ -32,7 +32,7 @@ class Plugins {
}
}
foreach (get_declared_classes() as $class) {
if (!$plugins[$class] && preg_match('~^Adminer\w~i', $class)) {
if (!$plugins[$class] && (preg_match('~^Adminer\w~i', $class) || is_subclass_of($class, 'Adminer\Plugin'))) {
// 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();
@@ -59,6 +59,13 @@ class Plugins {
}
}
/** Separate function to not overwrite local variables
* @return array<object>|true
*/
function includeOnce(string $filename) {
return include_once "./$filename";
}
/**
* @param literal-string $name
* @param mixed[] $params
@@ -67,7 +74,7 @@ class Plugins {
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
// some plugins accept params by reference - we don't need to propagate it outside, just to the other plugins
$args[] = &$params[$key];
}
$return = null;

View File

@@ -1,4 +1,4 @@
<?php
namespace Adminer;
const VERSION = "5.2.0-dev";
const VERSION = "5.4.2";

View File

@@ -4,6 +4,7 @@ namespace Adminer;
$TABLE = $_GET["indexes"];
$index_types = array("PRIMARY", "UNIQUE", "INDEX");
$table_status = table_status1($TABLE, true);
$index_algorithms = driver()->indexAlgorithms($table_status);
if (preg_match('~MyISAM|M?aria' . (min_version(5.6, '10.0.5') ? '|InnoDB' : '') . '~i', $table_status["Engine"])) {
$index_types[] = "FULLTEXT";
}
@@ -11,6 +12,7 @@ if (preg_match('~MyISAM|M?aria' . (min_version(5.7, '10.2.2') ? '|InnoDB' : '')
$index_types[] = "SPATIAL";
}
$indexes = indexes($TABLE);
$fields = fields($TABLE);
$primary = array();
if (JUSH == "mongo") { // doesn't support primary key
$primary = $indexes["_id_"];
@@ -29,13 +31,15 @@ if ($_POST && !$error && !$_POST["add"] && !$_POST["drop_col"]) {
$columns = array();
$lengths = array();
$descs = array();
$index_condition = (support("partial_indexes") ? $index["partial"] : "");
$index_algorithm = (in_array($index["algorithm"], $index_algorithms) ? $index["algorithm"] : "");
$set = array();
ksort($index["columns"]);
foreach ($index["columns"] as $key => $column) {
if ($column != "") {
$length = idx($index["lengths"], $key);
$desc = idx($index["descs"], $key);
$set[] = idf_escape($column) . ($length ? "(" . (+$length) . ")" : "") . ($desc ? " DESC" : "");
$set[] = ($fields[$column] ? idf_escape($column) : $column) . ($length ? "(" . (+$length) . ")" : "") . ($desc ? " DESC" : "");
$columns[] = $column;
$lengths[] = ($length ?: null);
$descs[] = $desc;
@@ -52,6 +56,8 @@ if ($_POST && !$error && !$_POST["add"] && !$_POST["drop_col"]) {
&& array_values($existing["columns"]) === $columns
&& (!$existing["lengths"] || array_values($existing["lengths"]) === $lengths)
&& array_values($existing["descs"]) === $descs
&& $existing["partial"] == $index_condition
&& (!$index_algorithms || $existing["algorithm"] == $index_algorithm)
) {
// skip existing index
unset($indexes[$name]);
@@ -59,7 +65,7 @@ if ($_POST && !$error && !$_POST["add"] && !$_POST["drop_col"]) {
}
}
if ($columns) {
$alter[] = array($index["type"], $name, $set);
$alter[] = array($index["type"], $name, $set, $index_algorithm, $index_condition);
}
}
}
@@ -76,7 +82,7 @@ if ($_POST && !$error && !$_POST["add"] && !$_POST["drop_col"]) {
page_header(lang('Indexes'), $error, array("table" => $TABLE), h($TABLE));
$fields = array_keys(fields($TABLE));
$fields_keys = array_keys($fields);
if ($_POST["add"]) {
foreach ($row["indexes"] as $key => $index) {
if ($index["columns"][count($index["columns"])] != "") {
@@ -105,20 +111,35 @@ $show_options = ($_POST ? $_POST["options"] : get_setting("index_options"));
<table class="nowrap">
<thead><tr>
<th id="label-type"><?php echo lang('Index Type'); ?>
<?php
$idxopts = " class='idxopts" . ($show_options ? "" : " hidden") . "'";
if ($index_algorithms) {
echo "<th id='label-algorithm'$idxopts>" . lang('Algorithm') . doc_link(array(
'sql' => 'create-index.html#create-index-storage-engine-index-types',
'mariadb' => 'storage-engine-index-types/',
'pgsql' => 'indexes-types.html',
));
}
?>
<th><input type="submit" class="wayoff"><?php
echo lang('Columns') . ($lengths ? "<span class='idxopts" . ($show_options ? "" : " hidden") . "'> (" . lang('length') . ")</span>" : "");
echo lang('Columns') . ($lengths ? "<span$idxopts> (" . lang('length') . ")</span>" : "");
if ($lengths || support("descidx")) {
echo checkbox("options", 1, $show_options, lang('Options'), "indexOptionsShow(this.checked)", "jsonly") . "\n";
}
?>
<th id="label-name"><?php echo lang('Name'); ?>
<?php
if (support("partial_indexes")) {
echo "<th id='label-condition'$idxopts>" . lang('Condition');
}
?>
<th><noscript><?php echo icon("plus", "add[0]", "+", lang('Add next')); ?></noscript>
</thead>
<?php
if ($primary) {
echo "<tr><td>PRIMARY<td>";
foreach ($primary["columns"] as $key => $column) {
echo select_input(" disabled", $fields, $column);
echo select_input(" disabled", $fields_keys, $column);
echo "<label><input disabled type='checkbox'>" . lang('descending') . "</label> ";
}
echo "<td><td>\n";
@@ -128,17 +149,21 @@ foreach ($row["indexes"] as $index) {
if (!$_POST["drop_col"] || $j != key($_POST["drop_col"])) {
echo "<tr><td>" . html_select("indexes[$j][type]", array(-1 => "") + $index_types, $index["type"], ($j == count($row["indexes"]) ? "indexesAddRow.call(this);" : ""), "label-type");
if ($index_algorithms) {
echo "<td$idxopts>" . html_select("indexes[$j][algorithm]", array_merge(array(""), $index_algorithms), $index['algorithm'], "label-algorithm");
}
echo "<td>";
ksort($index["columns"]);
$i = 1;
foreach ($index["columns"] as $key => $column) {
echo "<span>" . select_input(
" name='indexes[$j][columns][$i]' title='" . lang('Column') . "'",
($fields ? array_combine($fields, $fields) : $fields),
($fields && ($column == "" || $fields[$column]) ? array_combine($fields_keys, $fields_keys) : array()),
$column,
"partial(" . ($i == count($index["columns"]) ? "indexesAddColumn" : "indexesChangeColumn") . ", '" . js_escape(JUSH == "sql" ? "" : $_GET["indexes"] . "_") . "')"
);
echo "<span class='idxopts" . ($show_options ? "" : " hidden") . "'>";
echo "<span$idxopts>";
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>";
@@ -146,6 +171,9 @@ foreach ($row["indexes"] as $index) {
}
echo "<td><input name='indexes[$j][name]' value='" . h($index["name"]) . "' autocapitalize='off' aria-labelledby='label-name'>\n";
if (support("partial_indexes")) {
echo "<td$idxopts><input name='indexes[$j][partial]' value='" . h($index["partial"]) . "' autocapitalize='off' aria-labelledby='label-condition'>\n";
}
echo "<td>" . icon("cross", "drop_col[$j]", "x", lang('Remove')) . script("qsl('button').onclick = partial(editingRemoveRow, 'indexes\$1[type]');");
}
$j++;

View File

@@ -259,6 +259,56 @@ Lang::$translations = array(
'Permanent link' => 'স্থায়ী লিংক',
'Edit all' => 'সবগুলো সম্পাদনা করুন',
'HH:MM:SS' => 'HH:MM:SS',
'Check has been dropped.' => 'চেক ড্রপ করা হয়েছে।',
'Check has been altered.' => 'চেক পরিবর্তন করা হয়েছে।',
'Check has been created.' => 'চেক তৈরি করা হয়েছে।',
'Alter check' => 'চেক পরিবর্তন করুন',
'Create check' => 'চেক তৈরি করুন',
'Drop %s?' => '%s ড্রপ করবেন?',
'Tables have been optimized.' => 'টেবিলগুলি অপ্টিমাইজ করা হয়েছে।',
'Materialized view' => 'মেটেরিয়ালাইজড ভিউ',
'Vacuum' => 'ভ্যাকুয়াম',
'Selected' => 'নির্বাচিত',
'overwrite' => 'ওভাররাইট',
'DB' => 'ডিবি',
'Algorithm' => 'অ্যালগরিদম',
'Columns' => 'কলাম',
'Ctrl+click on a value to modify it.' => 'একটি মান পরিবর্তন করতে Ctrl+ক্লিক করুন।',
'File must be in UTF-8 encoding.' => 'ফাইলটি UTF-8 এনকোডিংয়ে হতে হবে।',
'Modify' => 'পরিবর্তন করুন',
'Load more data' => 'আরও ডেটা লোড করুন',
'Loading' => 'লোড হচ্ছে',
'ATTACH queries are not supported.' => 'ATTACH কোয়েরি সমর্থিত নয়।',
'Warnings' => 'সতর্কতা',
'%d / ' => array('%d / '),
'Limit rows' => 'সারি সীমিত করুন',
'Inherits from' => 'থেকে উত্তরাধিকারসূত্রে প্রাপ্ত',
'Checks' => 'চেকস',
'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>।',
'Default value' => 'ডিফল্ট মান',
'Full table scan' => 'সম্পূর্ণ টেবিল স্ক্যান',
'Too many unsuccessful logins, try again in %d minute(s).' => array('অনেকগুলি ব্যর্থ লগইন প্রচেষ্টা, %d মিনিট পরে আবার চেষ্টা করুন।'),
'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> কথা বিবেচনা করুন।',
'Master password expired. <a href="https://www.adminer.org/en/extension/"%s>Implement</a> %s method to make it permanent.' => 'মাস্টার পাসওয়ার্ডের মেয়াদ শেষ হয়েছে। এটিকে স্থায়ী করতে <a href="https://www.adminer.org/en/extension/"%s>ইমপ্লিমেন্ট</a> %s মেথড।',
'The action will be performed after successful login with the same credentials.' => 'একই ক্রেডেনশিয়ালস দিয়ে সফলভাবে লগইন করার পরে এই কর্মটি সম্পাদন করা হবে।',
'Connecting to privileged ports is not allowed.' => 'প্রিভিলেজড পোর্টে সংযোগ করা অনুমোদিত নয়।',
'There is a space in the input password which might be the cause.' => 'ইনপুট পাসওয়ার্ডে একটি স্পেস রয়েছে যা এর কারণ হতে পারে।',
'If you did not send this request from Adminer then close this page.' => 'আপনি যদি Adminer থেকে এই অনুরোধ না করে থাকেন তবে এই পৃষ্ঠাটি বন্ধ করুন।',
'You can upload a big SQL file via FTP and import it from server.' => 'আপনি FTP এর মাধ্যমে একটি বড় SQL ফাইল আপলোড করতে পারেন এবং সার্ভার থেকে এটি ইম্পোর্ট করতে পারেন।',
'Size' => 'আকার',
'Compute' => 'কম্পিউট',
'Loaded plugins' => 'লোড করা প্লাগইনগুলি',
'screenshot' => 'স্ক্রিনশট',
'You are offline.' => 'আপনি অফলাইনে আছেন।',
'You have no privileges to update this table.' => 'এই টেবিল আপডেট করার জন্য আপনার কোন অনুমতি নেই।',
'Saving' => 'সংরক্ষণ করা হচ্ছে',
'Unknown error.' => 'অজানা ত্রুটি।',
'%s must <a%s>return an array</a>.' => '%s অবশ্যই <a%s>একটি অ্যারে রিটার্ন করতে হবে</a>।',
'<a%s>Configure</a> %s in %s.' => '<a%s>কনফিগার করুন</a> %s এ %s।',
'Disable %s or enable %s or %s extensions.' => '%s নিষ্ক্রিয় করুন অথবা %s বা %s এক্সটেনশন সক্রিয় করুন।',
'Database does not support password.' => 'ডাটাবেস পাসওয়ার্ড সমর্থন করে না।',
'yes' => 'হ্যাঁ',
'no' => 'না',
);
// run `php ../../lang.php bn` to update this file

View File

@@ -13,9 +13,7 @@ Lang::$translations = array(
'Logged as: %s' => 'Přihlášen jako: %s',
'Logout successful.' => 'Odhlášení proběhlo v pořádku.',
'Thanks for using Adminer, consider <a href="https://www.adminer.org/en/donation/">donating</a>.' => 'Díky za použití Admineru, <a href="https://www.adminer.org/cs/donation/">přispějte</a> na vývoj.',
'Loaded plugins' => 'Nahrané pluginy',
'%s must <a%s>return an array</a>.' => '%s musí <a%s>vracet pole</a>.',
'<a%s>Configure</a> %s in %s.' => '<a%s>Nakonfigurujte</a> %s v %s.',
'hostname[:port] or :socket' => 'hostname[:port] nebo :socket',
'Invalid credentials.' => 'Neplatné přihlašovací údaje.',
'There is a space in the input password which might be the cause.' => 'Problém může být, že je v zadaném hesle mezera.',
'Adminer does not support accessing a database without a password, <a href="https://www.adminer.org/en/password/"%s>more information</a>.' => 'Adminer nepodporuje přístup k databázi bez hesla, <a href="https://www.adminer.org/cs/password/"%s>více informací</a>.',
@@ -30,7 +28,7 @@ Lang::$translations = array(
'Connecting to privileged ports is not allowed.' => 'Připojování k privilegovaným portům není povoleno.',
'Disable %s or enable %s or %s extensions.' => 'Zakažte %s nebo povolte rozšíření %s nebo %s.',
'Session support must be enabled.' => 'Session proměnné musí být povolené.',
'Session expired, please login again.' => 'Session vypršela, přihlašte se prosím znovu.',
'Session expired, please login again.' => 'Session vypršela, přihlaste se prosím znovu.',
'The action will be performed after successful login with the same credentials.' => 'Akce bude provedena po úspěšném přihlášení se stejnými přihlašovacími údaji.',
'%s version: %s through PHP extension %s' => 'Verze %s: %s přes PHP rozšíření %s',
'Refresh' => 'Obnovit',
@@ -79,6 +77,7 @@ Lang::$translations = array(
'Webserver file %s' => 'Soubor %s na webovém serveru',
'Run file' => 'Spustit soubor',
'File does not exist.' => 'Soubor neexistuje.',
'Increase %s.' => 'Zvyšte %s.',
'File uploads are disabled.' => 'Nahrávání souborů není povoleno.',
'Unable to upload a file.' => 'Nepodařilo se nahrát soubor.',
'Maximum allowed file size is %sB.' => 'Maximální povolená velikost souboru je %sB.',
@@ -197,6 +196,8 @@ Lang::$translations = array(
'Partitions' => 'Oddíly',
'Partition name' => 'Název oddílu',
'Values' => 'Hodnoty',
'Inherits from' => 'Zděděná z',
'Inherited by' => 'Zděděné',
'View' => 'Pohled',
'Materialized view' => 'Materializovaný pohled',
@@ -212,6 +213,8 @@ Lang::$translations = array(
'Add next' => 'Přidat další',
'Index Type' => 'Typ indexu',
'length' => 'délka',
'Algorithm' => 'Algoritmus',
'Condition' => 'Podmínka',
'Foreign keys' => 'Cizí klíče',
'Foreign key' => 'Cizí klíč',
@@ -352,6 +355,11 @@ Lang::$translations = array(
'Check has been created.' => 'Kontrola byla vytvořena.',
'Check has been altered.' => 'Kontrola byla změněna.',
'Check has been dropped.' => 'Kontrola byla odstraněna.',
'Loaded plugins' => 'Nahrané pluginy',
'%s must <a%s>return an array</a>.' => '%s musí <a%s>vracet pole</a>.',
'<a%s>Configure</a> %s in %s.' => '<a%s>Nakonfigurujte</a> %s v %s.',
'screenshot' => 'obrázek',
);
// run `php ../../lang.php cs` to update this file

View File

@@ -137,6 +137,7 @@ Lang::$translations = array(
'User has been created.' => 'Benutzer wurde erstellt.',
'Hashed' => 'Hashed',
'Column' => 'Spalte',
'Columns' => 'Spalten',
'Routine' => 'Routine',
'Grant' => 'Erlauben',
'Revoke' => 'Widerrufen',
@@ -305,6 +306,7 @@ Lang::$translations = array(
'Loaded plugins' => 'Geladene Plugins',
'%s must <a%s>return an array</a>.' => '%s muss <a%s>ein Array zurückgeben</a>.',
'<a%s>Configure</a> %s in %s.' => '<a%s>Konfigure</a> %s mit %s.',
'screenshot' => 'Screenshot',
);
// run `php ../../lang.php de` to update this file

314
adminer/lang/hi.inc.php Normal file
View File

@@ -0,0 +1,314 @@
<?php
namespace Adminer;
Lang::$translations = array(
'Login' => 'लॉगिन',
'Logout successful.' => 'सफलतापूर्वक लॉगआउट हो गया।',
'Invalid credentials.' => 'गलत पासवर्ड।',
'Server' => 'सर्वर',
'Username' => 'उपयोगकर्ता नाम',
'Password' => 'पासवर्ड',
'Select database' => 'डेटाबेस चुनें',
'Invalid database.' => 'अमान्य डेटाबेस।',
'Table has been dropped.' => 'टेबल हटा दिया गया है।',
'Table has been altered.' => 'टेबल बदल दिया गया है।',
'Table has been created.' => 'टेबल बनाया गया है।',
'Alter table' => 'टेबल बदलें',
'Create table' => 'टेबल बनाएं',
'Table name' => 'टेबल का नाम',
'engine' => 'इंजन',
'collation' => 'कॉलेशन',
'Column name' => 'कॉलम का नाम',
'Type' => 'प्रकार',
'Length' => 'लंबाई',
'Auto Increment' => 'ऑटो इंक्रीमेंट',
'Options' => 'विकल्प',
'Save' => 'सहेजें',
'Drop' => 'हटाएं',
'Database has been dropped.' => 'डेटाबेस हटा दिया गया है।',
'Database has been created.' => 'डेटाबेस बनाया गया है।',
'Database has been renamed.' => 'डेटाबेस का नाम बदल दिया गया है।',
'Database has been altered.' => 'डेटाबेस बदल दिया गया है।',
'Alter database' => 'डेटाबेस बदलें',
'Create database' => 'डेटाबेस बनाएं',
'SQL command' => 'SQL कमांड',
'Logout' => 'लॉगआउट',
'Use' => 'उपयोग करें',
'No tables.' => 'कोई टेबल नहीं।',
'select' => 'चुनें',
'Item has been deleted.' => 'आइटम हटा दिया गया है।',
'Item has been updated.' => 'आइटम अपडेट किया गया है।',
'Item%s has been inserted.' => 'आइटम%s डाला गया है।',
'Edit' => 'संपादित करें',
'Insert' => 'डालें',
'Save and insert next' => 'सहेजें और अगला डालें',
'Delete' => 'हटाएं',
'Database' => 'डेटाबेस',
'Routines' => 'रूटीन्स',
'Indexes have been altered.' => 'इंडेक्स बदल दिए गए हैं।',
'Indexes' => 'इंडेक्स',
'Alter indexes' => 'इंडेक्स बदलें',
'Add next' => 'अगला जोड़ें',
'Language' => 'भाषा',
'Select' => 'चुनें',
'New item' => 'नया आइटम',
'Search' => 'खोजें',
'Sort' => 'क्रमबद्ध करें',
'descending' => 'अवरोही',
'Limit' => 'सीमा',
'No rows.' => 'कोई पंक्ति नहीं।',
'Action' => 'कार्रवाई',
'edit' => 'संपादित करें',
'Page' => 'पृष्ठ',
'Query executed OK, %d row(s) affected.' => array('क्वेरी सफलतापूर्वक निष्पादित, %d पंक्ति प्रभावित।', 'क्वेरी सफलतापूर्वक निष्पादित, %d पंक्तियां प्रभावित।'),
'Error in query' => 'क्वेरी में त्रुटि',
'Execute' => 'निष्पादित करें',
'Table' => 'टेबल',
'Foreign keys' => 'फॉरेन की',
'Triggers' => 'ट्रिगर्स',
'View' => 'व्यू',
'Unable to select the table' => 'टेबल चुनने में असमर्थ',
'Invalid CSRF token. Send the form again.' => 'अमान्य CSRF टोकन। फॉर्म फिर से भेजें।',
'Comment' => 'टिप्पणी',
'Default values' => 'डिफ़ॉल्ट मान',
'%d byte(s)' => array('%d बाइट', '%d बाइट्स'),
'No commands to execute.' => 'निष्पादित करने के लिए कोई कमांड नहीं।',
'Unable to upload a file.' => 'फाइल अपलोड करने में असमर्थ।',
'File upload' => 'फाइल अपलोड',
'File uploads are disabled.' => 'फाइल अपलोड अक्षम हैं।',
'Routine has been called, %d row(s) affected.' => array('रूटीन कॉल किया गया, %d पंक्ति प्रभावित।', 'रूटीन कॉल किया गया, %d पंक्तियां प्रभावित।'),
'Call' => 'कॉल',
'No extension' => 'कोई एक्सटेंशन नहीं',
'None of the supported PHP extensions (%s) are available.' => 'कोई समर्थित PHP एक्सटेंशन (%s) उपलब्ध नहीं है।',
'Session support must be enabled.' => 'सेशन सपोर्ट सक्षम होना चाहिए।',
'Session expired, please login again.' => 'सेशन समाप्त, कृपया फिर से लॉगिन करें।',
'Text length' => 'टेक्स्ट लंबाई',
'Foreign key has been dropped.' => 'फॉरेन की हटा दी गई है।',
'Foreign key has been altered.' => 'फॉरेन की बदल दी गई है।',
'Foreign key has been created.' => 'फॉरेन की बनाई गई है।',
'Foreign key' => 'फॉरेन की',
'Target table' => 'लक्ष्य टेबल',
'Change' => 'बदलें',
'Source' => 'स्रोत',
'Target' => 'लक्ष्य',
'Add column' => 'कॉलम जोड़ें',
'Alter' => 'बदलें',
'Add foreign key' => 'फॉरेन की जोड़ें',
'ON DELETE' => 'ऑन डिलीट',
'ON UPDATE' => 'ऑन अपडेट',
'Index Type' => 'इंडेक्स प्रकार',
'length' => 'लंबाई',
'View has been dropped.' => 'व्यू हटा दिया गया है।',
'View has been altered.' => 'व्यू बदल दिया गया है।',
'View has been created.' => 'व्यू बनाया गया है।',
'Alter view' => 'व्यू बदलें',
'Create view' => 'व्यू बनाएं',
'Name' => 'नाम',
'Process list' => 'प्रक्रिया सूची',
'%d process(es) have been killed.' => array('%d प्रक्रिया समाप्त की गई है।', '%d प्रक्रियाएं समाप्त की गई हैं।'),
'Kill' => 'समाप्त करें',
'Parameter name' => 'पैरामीटर नाम',
'Database schema' => 'डेटाबेस स्कीमा',
'Create procedure' => 'प्रक्रिया बनाएं',
'Create function' => 'फंक्शन बनाएं',
'Routine has been dropped.' => 'रूटीन हटा दिया गया है।',
'Routine has been altered.' => 'रूटीन बदल दिया गया है।',
'Routine has been created.' => 'रूटीन बनाया गया है।',
'Alter function' => 'फंक्शन बदलें',
'Alter procedure' => 'प्रक्रिया बदलें',
'Return type' => 'वापसी प्रकार',
'Add trigger' => 'ट्रिगर जोड़ें',
'Trigger has been dropped.' => 'ट्रिगर हटा दिया गया है।',
'Trigger has been altered.' => 'ट्रिगर बदल दिया गया है।',
'Trigger has been created.' => 'ट्रिगर बनाया गया है।',
'Alter trigger' => 'ट्रिगर बदलें',
'Create trigger' => 'ट्रिगर बनाएं',
'Time' => 'समय',
'Event' => 'घटना',
'%s version: %s through PHP extension %s' => 'संस्करण %s: %s, PHP एक्सटेंशन %s के माध्यम से',
'%d row(s)' => array('%d पंक्ति', '%d पंक्तियां'),
'Remove' => 'हटाएं',
'Are you sure?' => 'क्या आप सुनिश्चित हैं?',
'Privileges' => 'विशेषाधिकार',
'Create user' => 'उपयोगकर्ता बनाएं',
'User has been dropped.' => 'उपयोगकर्ता हटा दिया गया है।',
'User has been altered.' => 'उपयोगकर्ता बदल दिया गया है।',
'User has been created.' => 'उपयोगकर्ता बनाया गया है।',
'Hashed' => 'हैश्ड',
'Column' => 'कॉलम',
'Routine' => 'रूटीन',
'Grant' => 'अनुदान',
'Revoke' => 'रद्द करें',
'Too big POST data. Reduce the data or increase the %s configuration directive.' => 'बहुत बड़ा POST डेटा। डेटा कम करें या %s कॉन्फ़िगरेशन निर्देश बढ़ाएं।',
'Logged as: %s' => '%s के रूप में लॉगिन',
'Move up' => 'ऊपर ले जाएं',
'Move down' => 'नीचे ले जाएं',
'Functions' => 'फंक्शन्स',
'Aggregation' => 'एग्रीगेशन',
'Export' => 'निर्यात',
'Output' => 'आउटपुट',
'open' => 'खोलें',
'save' => 'सहेजें',
'Format' => 'प्रारूप',
'Tables' => 'टेबल्स',
'Data' => 'डेटा',
'Event has been dropped.' => 'घटना हटा दी गई है।',
'Event has been altered.' => 'घटना बदल दी गई है।',
'Event has been created.' => 'घटना बनाई गई है।',
'Alter event' => 'घटना बदलें',
'Create event' => 'घटना बनाएं',
'At given time' => 'निर्धारित समय पर',
'Every' => 'हर',
'Events' => 'घटनाएं',
'Schedule' => 'अनुसूची',
'Start' => 'शुरू',
'End' => 'समाप्त',
'Status' => 'स्थिति',
'On completion preserve' => 'पूरा होने पर संरक्षित करें',
'Tables and views' => 'टेबल्स और व्यूज',
'Data Length' => 'डेटा लंबाई',
'Index Length' => 'इंडेक्स लंबाई',
'Data Free' => 'डेटा मुक्त',
'Collation' => 'कॉलेशन',
'Analyze' => 'विश्लेषण',
'Optimize' => 'अनुकूलित',
'Check' => 'जांच',
'Repair' => 'मरम्मत',
'Truncate' => 'ट्रंकेट',
'Tables have been truncated.' => 'टेबल्स ट्रंकेट कर दिए गए हैं।',
'Rows' => 'पंक्तियां',
',' => ',',
'0123456789' => '०१२३४५६७८९',
'Tables have been moved.' => 'टेबल्स स्थानांतरित कर दिए गए हैं।',
'Move to other database' => 'अन्य डेटाबेस में स्थानांतरित करें',
'Move' => 'स्थानांतरित करें',
'Engine' => 'इंजन',
'Save and continue edit' => 'सहेजें और संपादन जारी रखें',
'original' => 'मूल',
'Tables have been dropped.' => 'टेबल्स हटा दिए गए हैं।',
'%d item(s) have been affected.' => '%d आइटम प्रभावित हुए हैं।',
'Whole result' => 'पूरा परिणाम',
'Clone' => 'क्लोन',
'Maximum number of allowed fields exceeded. Please increase %s.' => 'अनुमत फील्ड्स की अधिकतम संख्या पार हो गई। कृपया %s बढ़ाएं।',
'Partition by' => 'द्वारा विभाजन',
'Partitions' => 'पार्टीशन्स',
'Partition name' => 'पार्टीशन नाम',
'Values' => 'मान',
'%d row(s) have been imported.' => array('%d पंक्ति आयात की गई है।', '%d पंक्तियां आयात की गई हैं।'),
'anywhere' => 'कहीं भी',
'Import' => 'आयात',
'Stop on error' => 'त्रुटि पर रुकें',
'%.3f s' => '%.3f सेकंड',
'$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.' => 'स्रोत और लक्ष्य कॉलम्स का डेटा प्रकार समान होना चाहिए, लक्ष्य कॉलम्स पर एक इंडेक्स होना चाहिए और संदर्भित डेटा मौजूद होना चाहिए।',
'Relations' => 'संबंध',
'Run file' => 'फाइल चलाएं',
'Clear' => 'साफ़ करें',
'Maximum allowed file size is %sB.' => 'अधिकतम अनुमत फाइल आकार %sB है।',
'Numbers' => 'संख्याएं',
'Date and time' => 'तिथि और समय',
'Strings' => 'स्ट्रिंग्स',
'Binary' => 'बाइनरी',
'Lists' => 'सूचियां',
'Editor' => 'संपादक',
'Webserver file %s' => 'वेबसर्वर फाइल %s',
'File does not exist.' => 'फाइल मौजूद नहीं है।',
'%d in total' => 'कुल %d',
'Permanent login' => 'स्थायी लॉगिन',
'Databases have been dropped.' => 'डेटाबेस हटा दिए गए हैं।',
'Search data in tables' => 'टेबल्स में डेटा खोजें',
'Schema' => 'स्कीमा',
'Alter schema' => 'स्कीमा बदलें',
'Create schema' => 'स्कीमा बनाएं',
'Schema has been dropped.' => 'स्कीमा हटा दी गई है।',
'Schema has been created.' => 'स्कीमा बनाई गई है।',
'Schema has been altered.' => 'स्कीमा बदल दी गई है।',
'Sequences' => 'अनुक्रम',
'Create sequence' => 'अनुक्रम बनाएं',
'Alter sequence' => 'अनुक्रम बदलें',
'Sequence has been dropped.' => 'अनुक्रम हटा दिया गया है।',
'Sequence has been created.' => 'अनुक्रम बनाया गया है।',
'Sequence has been altered.' => 'अनुक्रम बदल दिया गया है।',
'User types' => 'उपयोगकर्ता प्रकार',
'Create type' => 'प्रकार बनाएं',
'Alter type' => 'प्रकार बदलें',
'Type has been dropped.' => 'प्रकार हटा दिया गया है।',
'Type has been created.' => 'प्रकार बनाया गया है।',
'Use edit link to modify this value.' => 'इस मान को संशोधित करने के लिए संपादन लिंक का उपयोग करें।',
'last' => 'अंतिम',
'From server' => 'सर्वर से',
'System' => 'सिस्टम',
'Select data' => 'डेटा चुनें',
'Show structure' => 'संरचना दिखाएं',
'empty' => 'खाली',
'Network' => 'नेटवर्क',
'Geometry' => 'ज्यामिति',
'File exists.' => 'फाइल मौजूद है।',
'%d query(s) executed OK.' => array('%d क्वेरी सफलतापूर्वक निष्पादित।', '%d क्वेरीज़ सफलतापूर्वक निष्पादित।'),
'Show only errors' => 'केवल त्रुटियां दिखाएं',
'Refresh' => 'ताज़ा करें',
'Invalid schema.' => 'अमान्य स्कीमा।',
'Please use one of the extensions %s.' => 'कृपया %s एक्सटेंशन्स में से एक का उपयोग करें।',
'now' => 'अब',
'ltr' => 'ltr',
'Tables have been copied.' => 'टेबल्स कॉपी कर दिए गए हैं।',
'Copy' => 'कॉपी',
'Permanent link' => 'स्थायी लिंक',
'Edit all' => 'सभी संपादित करें',
'HH:MM:SS' => 'HH:MM:SS',
'Check has been dropped.' => 'चेक हटा दिया गया है।',
'Check has been altered.' => 'चेक को बदल दिया गया है।',
'Check has been created.' => 'चेक बनाया गया है।',
'Alter check' => 'चेक बदलें',
'Create check' => 'चेक बनाएँ',
'Drop %s?' => '%s हटाएँ?',
'Tables have been optimized.' => 'टेबल्स को ऑप्टिमाइज़ कर दिया गया है।',
'Materialized view' => 'मटेरियलाइज़्ड व्यू',
'Vacuum' => 'वैक्यूम',
'Selected' => 'चयनित',
'overwrite' => 'ओवरराइट',
'DB' => 'डेटाबेस',
'Algorithm' => 'एल्गोरिदम',
'Columns' => 'कॉलम',
'Ctrl+click on a value to modify it.' => 'किसी मान को संशोधित करने के लिए Ctrl+क्लिक करें।',
'File must be in UTF-8 encoding.' => 'फ़ाइल UTF-8 एन्कोडिंग में होनी चाहिए।',
'Modify' => 'संशोधित करें',
'Load more data' => 'और डेटा लोड करें',
'Loading' => 'लोड हो रहा है',
'ATTACH queries are not supported.' => 'संलग्न क्वेरीज़ समर्थित नहीं हैं।',
'Warnings' => 'चेतावनियाँ',
'%d / ' => '%d / ',
'Limit rows' => 'पंक्तियाँ सीमित करें',
'Inherits from' => 'इनहेरिट करता है',
'Checks' => 'चेक्स',
'Adminer does not support accessing a database without a password, <a href="https://www.adminer.org/en/password/"%s>more information</a>.' => 'एडमिनर बिना पासवर्ड के डेटाबेस एक्सेस करने का समर्थन नहीं करता, <a href="https://www.adminer.org/en/password/"%s>अधिक जानकारी</a>।',
'Default value' => 'डिफ़ॉल्ट मान',
'Full table scan' => 'पूरी टेबल स्कैन',
'Too many unsuccessful logins, try again in %d minute(s).' => 'बहुत अधिक असफल लॉगिन प्रयास, %d मिनट बाद पुनः प्रयास करें।',
'Thanks for using Adminer, consider <a href="https://www.adminer.org/en/donation/">donating</a>.' => 'एडमिनर उपयोग करने के लिए धन्यवाद, <a href="https://www.adminer.org/en/donation/">दान</a> करने पर विचार करें।',
'Master password expired. <a href="https://www.adminer.org/en/extension/"%s>Implement</a> %s method to make it permanent.' => 'मास्टर पासवर्ड समाप्त हो गया। इसे स्थायी बनाने के लिए %s मेथड <a href="https://www.adminer.org/en/extension/"%s>इम्प्लीमेंट</a> करें।',
'The action will be performed after successful login with the same credentials.' => 'यह क्रिया उसी क्रेडेंशियल्स से सफल लॉगिन के बाद की जाएगी।',
'Connecting to privileged ports is not allowed.' => 'प्रिविलेज्ड पोर्ट्स से कनेक्ट करने की अनुमति नहीं है।',
'There is a space in the input password which might be the cause.' => 'इनपुट पासवर्ड में एक स्पेस है जो कारण हो सकता है।',
'If you did not send this request from Adminer then close this page.' => 'अगर आपने यह अनुरोध एडमिनर से नहीं भेजा है तो इस पेज को बंद करें।',
'You can upload a big SQL file via FTP and import it from server.' => 'आप एक बड़ी SQL फ़ाइल FTP के माध्यम से अपलोड कर सकते हैं और सर्वर से इम्पोर्ट कर सकते हैं।',
'Size' => 'आकार',
'Compute' => 'कम्प्यूट',
'Loaded plugins' => 'लोडेड प्लगइन्स',
'screenshot' => 'स्क्रीनशॉट',
'You are offline.' => 'आप ऑफ़लाइन हैं।',
'You have no privileges to update this table.' => 'आपके पास इस टेबल को अपडेट करने की अनुमति नहीं है।',
'Saving' => 'सेव हो रहा है',
'Unknown error.' => 'अज्ञात त्रुटि।',
'%s must <a%s>return an array</a>.' => '%s को <a%s>एक ऐरे रिटर्न</a> करना चाहिए।',
'<a%s>Configure</a> %s in %s.' => '<a%s>कॉन्फ़िगर</a> %s में %s।',
'Disable %s or enable %s or %s extensions.' => '%s को डिसेबल करें या %s या %s एक्सटेंशन्स को एनेबल करें।',
'Database does not support password.' => 'डेटाबेस पासवर्ड का समर्थन नहीं करता।',
'yes' => 'हाँ',
'no' => 'नहीं',
);
// run `php ../../lang.php hi` to update this file

View File

@@ -5,13 +5,13 @@ Lang::$translations = array(
'Login' => 'ログイン',
'Logout successful.' => 'ログアウトしました。',
'Invalid credentials.' => '不正なログインです。',
'Server' => 'サーバ',
'Username' => 'ユーザ名',
'Server' => 'サーバ',
'Username' => 'ユーザ名',
'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>。',
'<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.' => 'データベースがパスワードに対応していません。',
@@ -20,14 +20,14 @@ Lang::$translations = array(
'Select database' => 'データベースを選択してください',
'Invalid database.' => '不正なデータベースです。',
'Table has been dropped.' => 'テーブルを削除しました。',
'Table has been altered.' => 'テーブルを変更しました。',
'Table has been altered.' => 'テーブルの設定を変更しました。',
'Table has been created.' => 'テーブルを作成しました。',
'Alter table' => 'テーブルを変更',
'Alter table' => 'テーブルの設定を変更',
'Create table' => 'テーブルを作成',
'Table name' => 'テーブル名',
'engine' => 'エンジン',
'collation' => '照合順序',
'Column name' => '名',
'collation' => 'コレーション',
'Column name' => 'カラム名',
'Type' => '型',
'Length' => '長さ',
'Auto Increment' => '連番',
@@ -38,16 +38,16 @@ Lang::$translations = array(
'Database has been dropped.' => 'データベースを削除しました。',
'Database has been created.' => 'データベースを作成しました。',
'Database has been renamed.' => 'データベースの名前を変えました。',
'Database has been altered.' => 'データベースを変更しました。',
'Alter database' => 'データベースを変更',
'Database has been altered.' => 'データベースの設定を変更しました。',
'Alter database' => 'データベースの設定を変更',
'Create database' => 'データベースを作成',
'SQL command' => 'SQLコマンド',
'Logout' => 'ログアウト',
'Use' => '使用',
'No tables.' => 'テーブルがありません。',
'select' => '選択',
'Item has been deleted.' => '項目を削除しました。',
'Item has been updated.' => '項目を更新しました。',
'Item has been deleted.' => 'レコードを削除しました。',
'Item has been updated.' => 'レコードを更新しました。',
'Edit' => '編集',
'Insert' => '挿入',
'Save and insert next' => '保存/追加',
@@ -56,18 +56,18 @@ Lang::$translations = array(
'Database' => 'データベース',
'DB' => 'DB',
'Routines' => 'ルーチン',
'Indexes have been altered.' => '索引を変更しました。',
'Indexes' => '索引',
'Alter indexes' => '索引の変更',
'Indexes have been altered.' => 'インデックスを変更しました。',
'Indexes' => 'インデックス',
'Alter indexes' => 'インデックスを変更',
'Add next' => '追加',
'Language' => '言語',
'Select' => '選択',
'New item' => '項目の作成',
'New item' => '新規レコードを挿入',
'Search' => '検索',
'Sort' => 'ソート',
'descending' => '降順',
'Limit' => '制約',
'Limit rows' => '行数の制約',
'Limit rows' => '表示行数を制限',
'No rows.' => '行がありません。',
'Action' => '動作',
'edit' => '編集',
@@ -82,17 +82,17 @@ Lang::$translations = array(
'Foreign keys' => '外部キー',
'Triggers' => 'トリガー',
'View' => 'ビュー',
'Materialized view' => 'マテビュー',
'Full table scan' => 'テーブル全スキャン',
'Materialized view' => 'マテリアライズドビュー',
'Full table scan' => 'テーブル全スキャン',
'Unable to select the table' => 'テーブルを選択できません',
'Invalid CSRF token. Send the form again.' => '不正なCSRFトークン再送信してください。',
'If you did not send this request from Adminer then close this page.' => 'Adminerからのリクエストを送信しない場合はこのページを閉じてください。',
'Invalid CSRF token. Send the form again.' => '不正なCSRFトークンです。フォームを再送信してください。',
'If you did not send this request from Adminer then close this page.' => 'Adminerからのリクエストない場合はこのページを閉じてください。',
'Comment' => 'コメント',
'Default values' => '定値',
'Default values' => '定値',
'%d byte(s)' => '%d バイト',
'No commands to execute.' => '実行するコマンドがありません。',
'Unable to upload a file.' => 'ファイルをアップロードできません。',
'File upload' => 'ファイルをアップロード',
'File upload' => 'アップロード',
'File uploads are disabled.' => 'ファイルのアップロードが無効です。',
'Routine has been called, %d row(s) affected.' => 'ルーチンを呼びました。%d 行を変更しました。',
'Call' => '呼出し',
@@ -103,21 +103,21 @@ Lang::$translations = array(
'Session support must be enabled.' => 'セッションを有効にしてください。',
'Session expired, please login again.' => 'セッションの期限切れ。ログインし直してください。',
'The action will be performed after successful login with the same credentials.' => '同じアカウントで正しくログインすると作業を実行します。',
'Text length' => '文字列の長さ',
'Text length' => '文字数を丸める',
'Foreign key has been dropped.' => '外部キーを削除しました。',
'Foreign key has been altered.' => '外部キーを変更しました。',
'Foreign key has been created.' => '外部キーを作成しました。',
'Foreign key' => '外キー',
'Target table' => 'テーブル',
'Foreign key' => '外キー',
'Target table' => '対象テーブル',
'Change' => '変更',
'Source' => 'ソース',
'Target' => 'ターゲット',
'Add column' => 'を追加',
'Add column' => 'カラムを追加',
'Alter' => '変更',
'Add foreign key' => '外部キーを追加',
'ON DELETE' => 'ON DELETE',
'ON UPDATE' => 'ON UPDATE',
'Index Type' => '索引の型',
'Index Type' => 'インデックスの型',
'length' => '長さ',
'View has been dropped.' => 'ビューを削除しました。',
'View has been altered.' => 'ビューを変更しました。',
@@ -126,25 +126,25 @@ Lang::$translations = array(
'Create view' => 'ビューを作成',
'Name' => '名称',
'Process list' => 'プロセス一覧',
'%d process(es) have been killed.' => '%d プロセスを強制終了しました。',
'Kill' => '強制終了',
'Parameter name' => '参数名',
'Database schema' => '構造',
'Create procedure' => 'プロシージャ作成',
'Create function' => '関数作成',
'Routine has been dropped.' => 'ルーチンを作成しました。',
'%d process(es) have been killed.' => '%d プロセスを終了しました。',
'Kill' => 'プロセスを終了',
'Parameter name' => 'パラメータ名',
'Database schema' => 'スキーマ',
'Create procedure' => 'プロシージャ作成',
'Create function' => '関数作成',
'Routine has been dropped.' => 'ルーチンを削除しました。',
'Routine has been altered.' => 'ルーチンを変更しました。',
'Routine has been created.' => 'ルーチンを作成しました。',
'Alter function' => '関数変更',
'Alter procedure' => 'プロシージャ変更',
'Alter function' => '関数変更',
'Alter procedure' => 'プロシージャ変更',
'Return type' => '戻り値の型',
'Add trigger' => 'トリガー追加',
'Add trigger' => 'トリガー追加',
'Trigger has been dropped.' => 'トリガーを削除しました。',
'Trigger has been altered.' => 'トリガーを変更しました。',
'Trigger has been created.' => 'トリガーを追加しました。',
'Alter trigger' => 'トリガー変更',
'Create trigger' => 'トリガー作成',
'Time' => '時間',
'Alter trigger' => 'トリガー変更',
'Create trigger' => 'トリガー作成',
'Time' => 'タイミング',
'Event' => 'イベント',
'%s version: %s through PHP extension %s' => '%sバージョン%s、 PHP拡張機能 %s',
'%d / ' => '%d / ',
@@ -157,10 +157,11 @@ Lang::$translations = array(
'User has been altered.' => 'ユーザを変更しました。',
'User has been created.' => 'ユーザを作成しました。',
'Hashed' => 'Hashed',
'Column' => '',
'Column' => 'カラム',
'Columns' => 'カラム',
'Routine' => 'ルーチン',
'Grant' => '権限付与',
'Revoke' => '権限の取消し',
'Grant' => '権限付与',
'Revoke' => '権限を取り消す',
'Logged as: %s' => 'ログ:%s',
'Too big POST data. Reduce the data or increase the %s configuration directive.' => 'POSTデータが大きすぎます。データサイズを小さくするか %s 設定を大きくしてください。',
'You can upload a big SQL file via FTP and import it from server.' => '大きなSQLファイルは、FTP経由でアップロードしてサーバからインポートしてください。',
@@ -171,16 +172,16 @@ Lang::$translations = array(
'Tables' => 'テーブル',
'Data' => 'データ',
'Output' => '出力',
'open' => '開く',
'open' => 'ブラウザに表示',
'save' => '保存',
'Format' => '形式',
'Functions' => '関数',
'Aggregation' => '集',
'Aggregation' => '集約関数',
'Event has been dropped.' => 'イベントを削除しました。',
'Event has been altered.' => 'イベントを変更しました。',
'Event has been created.' => 'イベントを作成しました。',
'Alter event' => '変更',
'Create event' => '作成',
'Alter event' => 'イベントを変更',
'Create event' => 'イベントを作成',
'Start' => '開始',
'End' => '終了',
'Every' => '毎回',
@@ -193,24 +194,24 @@ Lang::$translations = array(
'Tables have been moved.' => 'テーブルを移動しました。',
'Tables and views' => 'テーブルとビュー',
'Engine' => 'エンジン',
'Collation' => '照合順序',
'Collation' => 'コレーション',
'Data Length' => 'データ長',
'Index Length' => '索引長',
'Index Length' => 'インデックス長',
'Data Free' => '空き',
'Rows' => '行数',
',' => ',',
'0123456789' => '0123456789',
'Analyze' => '分析',
'Optimize' => '最適化',
'Vacuum' => '不要領域回収',
'Check' => 'チェック',
'Vacuum' => '不要領域回収(Vacuum)',
'Check' => '検査',
'Repair' => '修復',
'Truncate' => '空にする',
'Move to other database' => 'のデータベースへ移動',
'Move to other database' => 'のデータベースへ移動',
'Move' => '移動',
'Save and continue edit' => '保存して継続',
'original' => '元',
'%d item(s) have been affected.' => '%d を更新しました。',
'%d item(s) have been affected.' => '%d レコードを更新しました。',
'Whole result' => '全結果',
'Tables have been dropped.' => 'テーブルを削除しました。',
'Tables have been optimized.' => 'テーブルを最適化しました。',
@@ -222,7 +223,7 @@ Lang::$translations = array(
'Values' => '値',
'%d row(s) have been imported.' => '%d 行をインポートしました。',
'File must be in UTF-8 encoding.' => 'ファイルをUTF-8で保存してください。',
'Show structure' => '構造',
'Show structure' => 'スキーマ',
'anywhere' => '任意',
'Import' => 'インポート',
'Stop on error' => 'エラーの場合は停止',
@@ -232,9 +233,9 @@ Lang::$translations = array(
'[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.' => 'ソースとターゲットのは同じデータ型でなければなりません。ターゲット列に索引があり、データが存在しなければなりません。',
'Source and target columns must have the same data type, there must be an index on the target columns and referenced data must exist.' => 'ソースとターゲットのカラムは同じデータ型でなければなりません。ターゲットカラムにインデックスがあり、データが存在しなければなりません。',
'Relations' => '関係',
'Run file' => 'ファイルを実行',
'Run file' => '実行',
'Clear' => '消去',
'Maximum allowed file size is %sB.' => '最大ファイルサイズは %sB です。',
'Numbers' => '数字',
@@ -243,7 +244,7 @@ Lang::$translations = array(
'Binary' => 'バイナリ',
'Lists' => 'リスト',
'Editor' => 'エディタ',
'Webserver file %s' => 'Webサーバファイル %s',
'Webserver file %s' => 'ファイル %s',
'File does not exist.' => 'ファイルは存在しません。',
'%d in total' => '合計 %d',
'Permanent login' => '永続的にログイン',
@@ -267,15 +268,15 @@ Lang::$translations = array(
'Type has been dropped.' => 'ユーザー定義型を削除しました。',
'Type has been created.' => 'ユーザー定義型を追加しました。',
'Ctrl+click on a value to modify it.' => 'Ctrl+クリックで値を修正します。',
'Use edit link to modify this value.' => 'この値を修正するリンクを編集します。',
'Use edit link to modify this value.' => 'この値を修正するにはリンクを使用してください。',
'last' => '最終',
'From server' => 'サーバーから実行',
'From server' => 'サーバー上のファイル',
'System' => 'データベース種類',
'empty' => '空',
'Network' => 'ネットワーク型',
'Geometry' => 'ジオメトリ型',
'File exists.' => 'ファイルが既に存在します。',
'Item%s has been inserted.' => '%s項目を挿入しました。',
'Item%s has been inserted.' => '%sレコードを挿入しました。',
'now' => '現在の日時',
'%d query(s) executed OK.' => '%d クエリーを実行しました。',
'Show only errors' => 'エラーのみ表示',
@@ -286,27 +287,30 @@ Lang::$translations = array(
'Tables have been copied.' => 'テーブルをコピーしました。',
'Copy' => 'コピー',
'overwrite' => '上書き',
'Permanent link' => 'パーマネントリンク',
'Edit all' => 'すべて編集',
'Permanent link' => '固定リンク',
'Edit all' => '一括編集',
'Selected' => '選択中',
'Modify' => '編集',
'Load more data' => 'さらにデータを表示',
'Compute' => '再計算',
'Saving' => '保存しています...',
'Checks' => 'CHECK制約',
'Create check' => 'CHECK制約を追加',
'Alter check' => 'CHECK制約を編集',
'Check has been created.' => 'CHECK制約を追加しました。',
'Check has been altered.' => 'CHECK制約を編集しました。',
'Check has been dropped.' => 'CHECK制約を削除しました。',
'screenshot' => 'スクリーンショット',
'Algorithm' => 'アルゴリズム',
'Condition' => '条件',
'Inherits from' => '継承元',
'HH:MM:SS' => '時:分:秒',
'Selected' => '選択済',
'Modify' => '修正',
'Load more data' => '続きを読み込み',
'Loading' => '読み込み中',
'Size' => 'サイズ',
'Compute' => '算出',
'Saving' => '保存中',
'yes' => 'はい',
'no' => 'いいえ',
'Default value' => '既定値',
// Table check constraints
'Checks' => 'チェック',
'Create check' => 'チェックを作成',
'Alter check' => 'チェックを変更',
'Check has been created.' => 'チェックを作成しました。',
'Check has been altered.' => 'チェックを変更しました。',
'Check has been dropped.' => 'チェックを削除しました。',
);
// run `php ../../lang.php ja` to update this file

View File

@@ -13,9 +13,6 @@ Lang::$translations = array(
'Logged as: %s' => 'Zalogowany jako: %s',
'Logout successful.' => 'Wylogowano pomyślnie.',
'Thanks for using Adminer, consider <a href="https://www.adminer.org/en/donation/">donating</a>.' => 'Dziękujemy za używanie Adminera, rozważ <a href="https://www.adminer.org/pl/donation/">dotację</a>.',
'Loaded plugins' => 'Wczytane wtyczki',
'%s must <a%s>return an array</a>.' => '%s musi <a%s>zwrócić tablicę</a>.',
'<a%s>Configure</a> %s in %s.' => '<a%s>Skonfiguruj</a> %s w %s.',
'Invalid credentials.' => 'Nieprawidłowe dane logowania.',
'There is a space in the input password which might be the cause.' => 'W haśle wejściowym znajduje się spacja, która może być przyczyną.',
'Adminer does not support accessing a database without a password, <a href="https://www.adminer.org/en/password/"%s>more information</a>.' => 'Adminer nie obsługuje dostępu do bazy danych bez hasła, <a href="https://www.adminer.org/pl/password/"%s>więcej informacji</a>.',
@@ -79,6 +76,7 @@ Lang::$translations = array(
'Webserver file %s' => 'Plik %s na serwerze',
'Run file' => 'Uruchom z pliku',
'File does not exist.' => 'Plik nie istnieje.',
'Increase %s.' => 'Zwiększ %s.',
'File uploads are disabled.' => 'Wgrywanie plików jest wyłączone.',
'Unable to upload a file.' => 'Wgranie pliku było niemożliwe.',
'Maximum allowed file size is %sB.' => 'Maksymalna wielkość pliku to %sB.',
@@ -90,6 +88,7 @@ Lang::$translations = array(
'Output' => 'Rezultat',
'open' => 'otwórz',
'save' => 'zapisz',
'Saving' => 'Zapisywanie',
'Format' => 'Format',
'Data' => 'Dane',
@@ -197,6 +196,8 @@ Lang::$translations = array(
'Partitions' => 'Partycje',
'Partition name' => 'Nazwa partycji',
'Values' => 'Wartości',
'Inherits from' => 'Dziedziczy po',
'Inherited by' => 'Odziedziczone przez',
'View' => 'Perspektywa',
'Materialized view' => 'Zmaterializowana perspektywa',
@@ -212,6 +213,8 @@ Lang::$translations = array(
'Add next' => 'Dodaj następny',
'Index Type' => 'Typ indeksu',
'length' => 'długość',
'Algorithm' => 'Algorytm',
'Condition' => 'Warunek',
'Foreign keys' => 'Klucze obce',
'Foreign key' => 'Klucz obcy',
@@ -288,7 +291,6 @@ Lang::$translations = array(
'Edit' => 'Edytuj',
'Insert' => 'Dodaj',
'Save' => 'Zapisz zmiany',
'Saving' => 'Zapisywanie',
'Save and continue edit' => 'Zapisz i kontynuuj edycję',
'Save and insert next' => 'Zapisz i dodaj następny',
'Selected' => 'Zaznaczone',
@@ -352,6 +354,11 @@ Lang::$translations = array(
'Check has been created.' => 'Kontrola została utworzona.',
'Check has been altered.' => 'Kontrola została zmieniona.',
'Check has been dropped.' => 'Kontrola została usunięta.',
'Loaded plugins' => 'Wczytane wtyczki',
'%s must <a%s>return an array</a>.' => '%s musi <a%s>zwrócić tablicę</a>.',
'<a%s>Configure</a> %s in %s.' => '<a%s>Skonfiguruj</a> %s w %s.',
'screenshot' => 'zrzut ekranu',
);
// run `php ../../lang.php pl` to update this file

View File

@@ -13,9 +13,7 @@ Lang::$translations = array(
'Logged as: %s' => 'Xx: %s',
'Logout successful.' => 'Xx.',
'Thanks for using Adminer, consider <a href="https://www.adminer.org/en/donation/">donating</a>.' => 'Xx <a href="https://www.adminer.org/en/donation/">xx</a>.',
'Loaded plugins' => 'Xx',
'%s must <a%s>return an array</a>.' => '%s xx <a%s>xx</a>.',
'<a%s>Configure</a> %s in %s.' => '<a%s>Xx</a> %s xx %s.',
'hostname[:port] or :socket' => 'xx',
'Invalid credentials.' => 'Xx.',
'There is a space in the input password which might be the cause.' => 'Xx.',
'Adminer does not support accessing a database without a password, <a href="https://www.adminer.org/en/password/"%s>more information</a>.' => 'Xx, <a href="https://www.adminer.org/en/password/"%s>xx</a>.',
@@ -80,6 +78,7 @@ Lang::$translations = array(
'Webserver file %s' => 'Xx %s',
'Run file' => 'Xx',
'File does not exist.' => 'Xx.',
'Increase %s.' => 'Xx %s.',
'File uploads are disabled.' => 'Xx.',
'Unable to upload a file.' => 'Xx.',
'Maximum allowed file size is %sB.' => 'Xx %sB.',
@@ -199,6 +198,8 @@ Lang::$translations = array(
'Partitions' => 'Xx',
'Partition name' => 'Xx',
'Values' => 'Xx',
'Inherits from' => 'Xx',
'Inherited by' => 'Xx',
'View' => 'Xx',
'Materialized view' => 'Xx',
@@ -214,6 +215,8 @@ Lang::$translations = array(
'Add next' => 'Xx',
'Index Type' => 'Xx',
'length' => 'xx',
'Algorithm' => 'Xx',
'Condition' => 'Xx',
'Foreign keys' => 'Xx',
'Foreign key' => 'Xx',
@@ -354,6 +357,11 @@ Lang::$translations = array(
'Check has been created.' => 'Xx.',
'Check has been altered.' => 'Xx.',
'Check has been dropped.' => 'Xx.',
'Loaded plugins' => 'Xx',
'%s must <a%s>return an array</a>.' => '%s xx <a%s>xx</a>.',
'<a%s>Configure</a> %s in %s.' => '<a%s>Xx</a> %s xx %s.',
'screenshot' => 'xx',
);
// run `php ../../lang.php xx` to update this file

View File

@@ -56,13 +56,13 @@ echo ($collations ? "<datalist id='collations'>" . optionlist($collations) . "</
edit_fields($row["fields"], $collations, $routine);
if (isset($_GET["function"])) {
echo "<tr><td>" . lang('Return type');
edit_type("returns", $row["returns"], $collations, array(), (JUSH == "pgsql" ? array("void", "trigger") : array()));
edit_type("returns", (array) $row["returns"], $collations, array(), (JUSH == "pgsql" ? array("void", "trigger") : array()));
}
?>
</table>
<?php echo script("editFields();"); ?>
</div>
<p><?php textarea("definition", $row["definition"]); ?>
<p><?php textarea("definition", $row["definition"], 20); ?>
<p>
<input type="submit" value="<?php echo lang('Save'); ?>">
<?php if ($PROCEDURE != "") { ?>

View File

@@ -5,7 +5,7 @@ if (support("kill")) {
if ($_POST && !$error) {
$killed = 0;
foreach ((array) $_POST["kill"] as $val) {
if (kill_process($val)) {
if (adminer()->killProcess($val)) {
$killed++;
}
}
@@ -23,7 +23,7 @@ page_header(lang('Process list'), $error);
echo script("mixin(qsl('table'), {onclick: tableClick, ondblclick: partialArg(tableClick, true)});");
// HTML valid because there is always at least one process
$i = -1;
foreach (process_list() as $i => $row) {
foreach (adminer()->processList() as $i => $row) {
if (!$i) {
echo "<thead><tr lang='en'>" . (support("kill") ? "<th>" : "");
foreach ($row as $key => $val) {

View File

@@ -18,7 +18,8 @@ $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
/** @var array<numeric-string, bool> */
$lefts = array();
$all_fields = driver()->allFields();
foreach (table_status('', true) as $table => $table_status) {
if (is_view($table_status)) {

View File

@@ -7,7 +7,7 @@ if ($_GET["script"] == "db") {
$sums = array("Data_length" => 0, "Index_length" => 0, "Data_free" => 0);
foreach (table_status() as $name => $table_status) {
json_row("Comment-$name", h($table_status["Comment"]));
if (!is_view($table_status)) {
if (!is_view($table_status) || preg_match('~materialized~i', $table_status["Engine"])) {
foreach (array("Engine", "Collation") as $key) {
json_row("$key-$name", h($table_status[$key]));
}

View File

@@ -54,7 +54,7 @@ if ($_GET["val"] && is_ajax()) {
exit;
}
$primary = $unselected = null;
$primary = $unselected = array();
foreach ($indexes as $index) {
if ($index["type"] == "PRIMARY") {
$primary = array_flip($index["columns"]);
@@ -353,17 +353,14 @@ if (!$columns && support("table")) {
$desc = "&desc%5B0%5D=1";
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) {
echo "<a href='" . h($href . $desc) . "' title='" . lang('descending') . "' class='text'> ↓</a>";
}
$sortable = isset($field["privileges"]["order"]) || $fun != $name;
echo ($sortable ? "<a href='" . h($href . ($order[0] == $column || $order[0] == $key ? $desc : '')) . "'>$fun</a>" : $fun); // $order[0] == $key - COUNT(*)
$menu = ($sortable ? "<a href='" . h($href . $desc) . "' title='" . lang('descending') . "' class='text'> ↓</a>" : '');
if (!$val["fun"] && isset($field["privileges"]["where"])) {
echo '<a href="#fieldset-search" title="' . lang('Search') . '" class="text jsonly"> =</a>';
echo script("qsl('a').onclick = partial(selectSearch, '" . js_escape($key) . "');");
$menu .= '<a href="#fieldset-search" title="' . lang('Search') . '" class="text jsonly"> =</a>';
$menu .= script("qsl('a').onclick = partial(selectSearch, '" . js_escape($key) . "');");
}
echo "</span>";
echo ($menu ? "<span class='column hidden'>$menu</span>" : "");
}
$functions[$key] = $val["fun"];
next($select);
@@ -389,10 +386,12 @@ if (!$columns && support("table")) {
$unique_array = unique_array($rows[$n], $indexes);
if (!$unique_array) {
$unique_array = array();
reset($select);
foreach ($rows[$n] as $key => $val) {
if (!preg_match('~^(COUNT\((\*|(DISTINCT )?`(?:[^`]|``)+`)\)|(AVG|GROUP_CONCAT|MAX|MIN|SUM)\(`(?:[^`]|``)+`\))$~', $key)) { //! columns looking like functions
if (!preg_match('~^(COUNT|AVG|GROUP_CONCAT|MAX|MIN|SUM)\(~', current($select))) {
$unique_array[$key] = $val;
}
next($select);
}
}
$unique_idf = "";
@@ -410,16 +409,17 @@ if (!$columns && support("table")) {
. ($is_group || information_schema(DB) ? "" : " <a href='" . h(ME . "edit=" . urlencode($TABLE) . $unique_idf) . "' class='edit'>" . lang('edit') . "</a>")
);
reset($select);
foreach ($row as $key => $val) {
if (isset($names[$key])) {
$column = current($select);
$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
}
$link = "";
if (preg_match('~blob|bytea|raw|file~', $field["type"]) && $val != "") {
if (is_blob($field) && $val != "") {
$link = ME . 'download=' . urlencode($TABLE) . '&field=' . urlencode($key) . $unique_idf;
}
if (!$link && $val !== null) { // link related items
@@ -439,7 +439,7 @@ if (!$columns && support("table")) {
}
}
}
if ($key == "COUNT(*)") { //! columns looking like functions
if ($column == "COUNT(*)") {
$link = ME . "select=" . urlencode($TABLE);
$i = 0;
foreach ((array) $_GET["where"] as $v) {
@@ -456,8 +456,10 @@ if (!$columns && support("table")) {
$id = h("val[$unique_idf][" . bracket_escape($key) . "]");
$posted = idx(idx($_POST["val"], $unique_idf), bracket_escape($key));
$editable = !is_array($row[$key]) && is_utf8($html) && $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"]) && ($val === null || is_numeric(strip_tags($html))) ? " class='number'" : "");
$type = (preg_match('~^(AVG|MIN|MAX)\((.+)\)~', $column, $match) ? $fields[idf_unescape($match[2])]["type"] : $field["type"]);
$text = preg_match('~text|json|lob~', $type);
$is_number = preg_match(number_type(), $type) || preg_match('~^(CHAR_LENGTH|ROUND|FLOOR|CEIL|TIME_TO_SEC|COUNT|SUM)\(~', $column);
echo "<td id='$id'" . ($is_number && ($val === null || is_numeric(strip_tags($html)) || $type == "money") ? " class='number'" : "");
if (($_GET["modify"] && $editable && $val !== null) || $posted !== null) {
$h_value = h($posted !== null ? $posted : $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]'>");
@@ -469,6 +471,7 @@ if (!$columns && support("table")) {
;
}
}
next($select);
}
if ($backward_keys) {
@@ -496,7 +499,7 @@ if (!$columns && support("table")) {
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 {
} elseif (JUSH == 'sql' || JUSH == 'pgsql') {
$exact_count = false;
}
}
@@ -584,15 +587,15 @@ if (!$columns && support("table")) {
}
if (adminer()->selectImportPrint()) {
echo "<div>";
echo "<p>";
echo "<a href='#import'>" . lang('Import') . "</a>";
echo script("qsl('a').onclick = partial(toggle, 'import');", "");
echo "<span id='import'" . ($_POST["import"] ? "" : " class='hidden'") . ">: ";
echo "<input type='file' name='csv_file'> ";
echo html_select("separator", array("csv" => "CSV,", "csv;" => "CSV;", "tsv" => "TSV"), $adminer_import["format"]);
echo " <input type='submit' name='import' value='" . lang('Import') . "'>";
echo file_input("<input type='file' name='csv_file'> "
. html_select("separator", array("csv" => "CSV,", "csv;" => "CSV;", "tsv" => "TSV"), $adminer_import["format"])
. " <input type='submit' name='import' value='" . lang('Import') . "'>")
;
echo "</span>";
echo "</div>";
}
echo input_token();

View File

@@ -4,9 +4,13 @@ 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();
if ($_POST["format"] == "sql") {
echo "$_POST[query]\n";
} else {
adminer()->dumpTable("", "");
adminer()->dumpData("", "table", $_POST["query"]);
adminer()->dumpFooter();
}
exit;
}
@@ -20,6 +24,7 @@ if (!$error && $_POST["clear"]) {
stop_session();
page_header((isset($_GET["import"]) ? lang('Import') : lang('SQL command')), $error);
$line_comment = '--' . (JUSH == 'sql' ? ' ' : '');
if (!$error && $_POST) {
$fp = false;
@@ -51,8 +56,8 @@ if (!$error && $_POST) {
}
}
$space = "(?:\\s|/\\*[\s\S]*?\\*/|(?:#|-- )[^\n]*\n?|--\r?\n)";
$delimiter = ";";
$space = "(?:\\s|/\\*[\s\S]*?\\*/|(?:#|$line_comment)[^\n]*\n?|--\r?\n)";
$delimiter = driver()->delimiter;
$offset = 0;
$empty = true;
$connection2 = connect(); // connection for exploring indexes and EXPLAIN (to not replace FOUND_ROWS()) //! PDO - silent error
@@ -64,11 +69,9 @@ if (!$error && $_POST) {
}
$commands = 0;
$errors = array();
$parse = '[\'"' . (JUSH == "sql" ? '`#' : (JUSH == "sqlite" ? '`[' : (JUSH == "mssql" ? '[' : ''))) . ']|/\*|--' . (JUSH == 'sql' ? ' ' : '') . '|$' . (JUSH == "pgsql" ? '|\$[^$]*\$' : '');
$parse = '[\'"' . (JUSH == "sql" ? '`#' : (JUSH == "sqlite" ? '`[' : (JUSH == "mssql" ? '[' : ''))) . ']|/\*|' . $line_comment . '|$' . (JUSH == "pgsql" ? '|\$([a-zA-Z]\w*)?\$' : '');
$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();
unset($dump_format["sql"]);
while ($query != "") {
if (!$offset && preg_match("~^$space*+DELIMITER\\s+(\\S+)~i", $query, $match)) {
@@ -94,7 +97,7 @@ if (!$error && $_POST) {
$pattern =
($found == '/*' ? '\*/' :
($found == '[' ? ']' :
(preg_match('~^-- |^#~', $found) ? "\n" :
(preg_match("~^$line_comment|^#~", $found) ? "\n" :
preg_quote($found) . ($c_style_escapes ? '|\\\\.' : ''))))
;
@@ -173,7 +176,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("format", $dump_format, $adminer_export["format"])
. html_select("format", adminer()->dumpFormat(), $adminer_export["format"])
. input_hidden("query", $q)
. "<input type='submit' name='export' value='" . lang('Export') . "'>" . input_token() . "</span>\n"
. "</form>\n"
@@ -247,12 +250,9 @@ if (!isset($_GET["import"])) {
echo lang('Limit rows') . ": <input type='number' name='limit' class='size' value='" . h($_POST ? $_POST["limit"] : $_GET["limit"]) . "'>\n";
} else {
echo "<fieldset><legend>" . lang('File upload') . "</legend><div>";
$gz = (extension_loaded("zlib") ? "[.gz]" : "");
echo (ini_bool("file_uploads")
? "SQL$gz (&lt; " . ini_get("upload_max_filesize") . "B): <input type='file' name='sql_file[]' multiple>\n$execute" // ignore post_max_size because it is for all form fields together and bytes computing would be necessary
: lang('File uploads are disabled.')
);
echo "<fieldset><legend>" . lang('File upload') . "</legend><div>";
echo file_input("SQL$gz: <input type='file' name='sql_file[]' multiple>\n$execute");
echo "</div></fieldset>\n";
$importServerPath = adminer()->importServerPath();
if ($importServerPath) {
@@ -275,7 +275,7 @@ if (!isset($_GET["import"]) && $history) {
list($q, $time, $elapsed) = $val;
echo '<a href="' . h(ME . "sql=&history=$key") . '">' . lang('Edit') . "</a>"
. " <span class='time' title='" . @date('Y-m-d', $time) . "'>" . @date("H:i:s", $time) . "</span>" // @ - time zone may be not set
. " <code class='jush-" . JUSH . "'>" . shorten_utf8(ltrim(str_replace("\n", " ", str_replace("\r", "", preg_replace('~^(#|-- ).*~m', '', $q)))), 80, "</code>")
. " <code class='jush-" . JUSH . "'>" . shorten_utf8(ltrim(str_replace("\n", " ", str_replace("\r", "", preg_replace("~^(#|$line_comment).*~m", '', $q)))), 80, "</code>")
. ($elapsed ? " <span class='time'>($elapsed)</span>" : "")
. "<br>\n"
;

View File

@@ -4,17 +4,16 @@ html {
--bg: #002240;
--fg: #829bb0;
--dim: #154269;
--lit: #011d35;
}
a { color: #618CB3; }
a:visited { color: #618CB3; }
a, a:visited { color: #618cb3; }
a:link:hover, a:visited:hover { color: #9bc0e1; }
h1 { border-color: #5e94c1; color: #ffddbf; }
h2 { border-color: #a3bdd3; color: #000; background: #3c678d; }
table, td, th { border-color: #0e416d; }
table, td, th, .js .column { border-color: #0e416d; }
th { background: #11385a; }
thead td, thead th { color: #a8b05f; background: #011d35; }
thead th a { color: #a8b05f; }
thead td, thead th, thead th a { color: #a8b05f; }
fieldset { border-color: #16548a; }
code { background: #11385a; }
tbody tr:hover td, tbody tr:hover th { background: #133553; }
@@ -22,7 +21,6 @@ pre.jush { background: #11385a; }
input.default { box-shadow: 1px 1px 1px #888; }
input.required, input.maxlength { box-shadow: 1px 1px 1px red; }
.version { color: #888; }
.js .column { background: #011d35; }
.error { color: red; background: #efdada; border: 1px solid #e76f6f; }
.error b { background: #efeaea; }
.message { color: #0b860b; background: #efe; border: 1px solid #7fbd7f; }
@@ -45,3 +43,4 @@ input.required, input.maxlength { box-shadow: 1px 1px 1px red; }
#schema div.table a { color: #3c7bb3; }
#menu .active { color: #398c8d; }
#edit-fields tbody tr:hover td, #edit-fields tbody tr:hover th { background: #3b6f9d; }
:target { color: #a8b05f; }

View File

@@ -4,6 +4,7 @@ html {
--bg: #fff;
--fg: #000;
--dim: #eee;
--lit: #ddf;
}
body { color: var(--fg); background: var(--bg); font: 90%/1.25 Verdana, Arial, Helvetica, sans-serif; margin: 0; min-width: fit-content; }
@@ -13,17 +14,17 @@ 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 .667em; border-bottom: 1px solid #999; font-weight: normal; color: #777; background: var(--dim); }
h2 { font-size: 150%; margin: 0 0 20px -18px; padding: .8em 1em; border-bottom: 1px solid var(--fg); 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: var(--lit); }
h3 { font-weight: normal; font-size: 130%; margin: 1em 0 0; }
form { margin: 0; }
td table { width: 100%; margin: 0; }
table { margin: 1em 20px 0 0; font-size: 90%; border-spacing: 0; border-width: 1px 0 0 1px; }
table, td, th { border-color: #999; border-style: solid; }
table, td, th, .js .column { border-color: #999; border-style: solid; }
td, th { border-width: 0 1px 1px 0; padding: .2em .3em; margin: 0; }
th { background: var(--dim); text-align: left; }
thead { position: sticky; top: 0; }
thead th { text-align: center; padding: .2em .5em; }
thead td, thead th { background: #ddf; }
thead td, thead th { background: var(--lit); }
fieldset { display: inline; vertical-align: top; padding: .5em .8em; margin: .8em .5em 0 0; border: 1px solid #999; border-radius: 5px; }
p { margin: .8em 20px 0 0; }
img { vertical-align: middle; border: 0; }
@@ -44,7 +45,7 @@ input.wayoff { left: -1000px; position: absolute; }
.block { display: block; }
.version { color: #777; font-size: 62%; }
.js .hidden, .nojs .jsonly { display: none; }
.js .column { position: absolute; background: #ddf; padding: .27em 1ex .3em 0; margin-top: -.27em; }
.js .column { position: absolute; background: var(--lit); padding: .27em 1ex .33em 0; margin-top: -.37em; border-width: 1px 1px 1px 0; border-radius: 0 8px 8px 0; }
.nowrap td, .nowrap th, td.nowrap, p.nowrap { white-space: pre; }
.wrap td { white-space: normal; }
.error { color: red; background: #fee; }
@@ -56,8 +57,9 @@ input.wayoff { left: -1000px; position: absolute; }
.date { color: #7F007F; }
.enum { color: #007F7F; }
.binary { color: red; }
.odds tbody tr { background: var(--bg); }
.odds tbody tr:nth-child(2n) { background: #F5F5F5; }
.js .checkable .checked td, .js .checkable .checked th { background: #ddf; }
.js .checkable .checked td, .js .checkable .checked th { background: var(--lit); }
.time { color: silver; font-size: 70%; }
.function, .number, .datetime { text-align: right; }
.type { width: 15ex; }
@@ -67,12 +69,12 @@ 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: navy center no-repeat; border: 0; vertical-align: middle; }
.icon { width: 18px; height: 18px; background: navy center no-repeat; border: 0; padding: 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: 1em -20px .5em 0; box-shadow: 0 -5px 10px 10px var(--bg); }
.footer { position: sticky; bottom: 0; margin: 23px -20px .5em 0; box-shadow: 0 -5px 10px 10px var(--bg); }
.footer > div { background: var(--bg); padding: 0 0 .5em; }
.footer fieldset { margin-top: 0; }
.links a { white-space: nowrap; margin-right: 20px; }
@@ -96,6 +98,7 @@ input.wayoff { left: -1000px; position: absolute; }
#schema .table { border: 1px solid silver; padding: 0 2px; cursor: move; position: absolute; }
#schema .references { position: absolute; }
#help { position: absolute; border: 1px solid #999; background: var(--dim); padding: 5px; font-family: monospace; z-index: 1; }
:target { background: var(--lit); }
/* 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==); }
@@ -126,14 +129,18 @@ input.wayoff { left: -1000px; position: absolute; }
.js .foot { display: none; }
.js #menuopen { display: block; position: absolute; top: 3px; left: 6px; }
.nojs #menu { position: static; }
.rtl.js #foot { left: auto; right: 0; }
.rtl .pages { right: auto; }
.rtl #content { margin-right: 10px; }
.rtl #breadcrumb { right: auto; }
.rtl.js #menuopen { left: auto; right: 6px; }
.rtl #content { margin-left: 0 !important; margin-right: 10px; }
.rtl #breadcrumb { left: auto !important; right: 48px; }
}
@media print {
#lang, #menu { display: none; }
#lang, #menu, .logout { display: none; }
#content { margin-left: 1em; }
#breadcrumb { left: 1em; }
.rtl #content { margin-left: auto; margin-right: 1em; }
.rtl #breadcrumb { left: auto; right: 1em; }
.nowrap td, .nowrap th, td.nowrap { white-space: normal; }
}

View File

@@ -93,6 +93,13 @@ function messagesPrint(parent) {
for (const el of qsa('.toggle', parent)) {
el.onclick = partial(toggle, el.getAttribute('href').substr(1));
}
for (const el of qsa('.copy', parent)) {
el.onclick = () => {
navigator.clipboard.writeText(qs('code', el.parentElement).innerText).then(() => el.textContent = '✓');
setTimeout(() => el.textContent = '🗐', 1000);
return false;
};
}
}
@@ -384,7 +391,7 @@ function editingAddRow(focus) {
*/
function editingRemoveRow(name) {
const field = formField(this.form, this.name.replace(/[^[]+(.+)/, name));
field.parentNode.removeChild(field);
field.remove();
parentTag(this, 'tr').style.display = 'none';
return false;
}
@@ -456,7 +463,7 @@ function editingLengthChange() {
*/
function editingLengthFocus() {
const td = this.parentNode;
if (/(enum|set)$/.test(selectValue(td.previousSibling.firstChild))) {
if (/^(enum|set)$/.test(selectValue(td.previousSibling.firstChild))) {
const edit = qs('#enum-edit');
edit.value = enumValues(this.value);
td.appendChild(edit);
@@ -655,13 +662,29 @@ function indexesAddColumn(prefix) {
* @param string
*/
function sqlSubmit(form, root) {
if (encodeURIComponent(form['query'].value).length < 500) {
form.action = root
+ '&sql=' + encodeURIComponent(form['query'].value)
+ (form['limit'].value ? '&limit=' + +form['limit'].value : '')
+ (form['error_stops'].checked ? '&error_stops=1' : '')
+ (form['only_errors'].checked ? '&only_errors=1' : '')
;
const action = root
+ '&sql=' + encodeURIComponent(form['query'].value)
+ (form['limit'].value ? '&limit=' + +form['limit'].value : '')
+ (form['error_stops'].checked ? '&error_stops=1' : '')
+ (form['only_errors'].checked ? '&only_errors=1' : '')
;
if ((document.location.origin + document.location.pathname + action).length < 2000) { // reasonable minimum is 2048
form.action = action;
}
}
/** Check if PHP can handle the uploaded files
* @param Event
* @param number
* @param string
* @param number
* @param string
*/
function fileChange(event, count, countMessage, size, sizeMessage) {
if (event.target.files.length > count) {
alert(countMessage);
} else if (Array.from(event.target.files).reduce((sum, file) => sum + file.size, 0) > size) {
alert(sizeMessage);
}
}

View File

@@ -96,29 +96,15 @@ function cookie(assign, days) {
/** Verify current Adminer version
* @param string
* @param string own URL base
* @param string
*/
function verifyVersion(current, url, token) {
function verifyVersion(current) {
cookie('adminer_version=0', 1);
const iframe = document.createElement('iframe');
iframe.src = 'https://www.adminer.org/version/?current=' + current;
iframe.frameBorder = 0;
iframe.marginHeight = 0;
iframe.scrolling = 'no';
iframe.style.width = '7ex';
iframe.style.height = '1.25em';
iframe.style.display = 'none';
addEventListener('message', event => {
if (event.origin == 'https://www.adminer.org') {
const match = /version=(.+)/.exec(event.data);
if (match) {
cookie('adminer_version=' + match[1], 1);
ajax(url + 'script=version', () => { }, event.data + '&token=' + token);
}
}
}, false);
qs('#version').appendChild(iframe);
// do not send X-Requested-With to avoid preflight
fetch('https://www.adminer.org/version/?current=' + current).then(async response => {
const json = await response.json();
cookie('adminer_version=' + (json.version || current), 7); // empty if there's no newer version
qs('#version').textContent = json.version;
});
}
/** Get value of select
@@ -235,8 +221,8 @@ function formChecked(input, name) {
function tableClick(event, click) {
const td = parentTag(event.target, 'td');
let text;
if (td && (text = td.getAttribute('data-text'))) {
if (selectClick.call(td, event, +text, td.getAttribute('data-warning'))) {
if (td && (text = td.dataset.text)) {
if (selectClick.call(td, event, +text, td.dataset.warning)) {
return;
}
}
@@ -525,14 +511,14 @@ function functionChange() {
if (selectValue(this)) {
if (input.origType === undefined) {
input.origType = input.type;
input.origMaxLength = input.getAttribute('data-maxlength');
input.origMaxLength = input.dataset.maxlength;
}
input.removeAttribute('data-maxlength');
delete input.dataset.maxlength;
input.type = 'text';
} else if (input.origType) {
input.type = input.origType;
if (input.origMaxLength >= 0) {
input.setAttribute('data-maxlength', input.origMaxLength);
input.dataset.maxlength = input.origMaxLength;
}
}
oninput({target: input});
@@ -736,9 +722,10 @@ function selectLoadMore(limit, loading) {
return !ajax(href, request => {
const tbody = document.createElement('tbody');
tbody.innerHTML = request.responseText;
adminerHighlighter(qsa('code', tbody));
qs('#table').appendChild(tbody);
if (tbody.children.length < limit) {
a.parentNode.removeChild(a);
a.remove();
} else {
a.href = href.replace(/\d+$/, page => +page + 1);
a.innerHTML = title;
@@ -843,7 +830,7 @@ function cloneNode(el) {
oninput = event => {
const target = event.target;
const maxLength = target.getAttribute('data-maxlength');
const maxLength = target.dataset.maxlength;
alterClass(target, 'maxlength', target.value && maxLength != null && target.value.length > maxLength); // maxLength could be 0
};

View File

@@ -26,11 +26,29 @@ if ($fields) {
adminer()->tableStructurePrint($fields, $table_status);
}
/** Print links to tables
* @param list<array{table: string, ns: string}> $tables
*/
function tables_links(array $tables): void {
echo "<ul>\n";
foreach ($tables as $row) {
$link = preg_replace('~ns=[^&]*~', "ns=" . urlencode($row["ns"]), ME);
echo "<li><a href='" . h($link . "table=" . urlencode($row["table"])) . "'>" . ($row["ns"] != $_GET["ns"] ? "<b>" . h($row["ns"]) . "</b>." : "") . h($row["table"]) . "</a>";
}
echo "</ul>\n";
}
$inherits = driver()->inheritsFrom($TABLE);
if ($inherits) {
echo "<h3>" . lang('Inherits from') . "</h3>\n";
tables_links($inherits);
}
if (support("indexes") && driver()->supportsIndex($table_status)) {
echo "<h3 id='indexes'>" . lang('Indexes') . "</h3>\n";
$indexes = indexes($TABLE);
if ($indexes) {
adminer()->tableIndexesPrint($indexes);
adminer()->tableIndexesPrint($indexes, $table_status);
}
echo '<p class="links"><a href="' . h(ME) . 'indexes=' . urlencode($TABLE) . '">' . lang('Alter indexes') . "</a>\n";
}
@@ -95,3 +113,13 @@ if (support(is_view($table_status) ? "view_trigger" : "trigger")) {
}
echo '<p class="links"><a href="' . h(ME) . 'trigger=' . urlencode($TABLE) . '">' . lang('Add trigger') . "</a>\n";
}
$inherited = driver()->inheritedTables($TABLE);
if ($inherited) {
echo "<h3 id='partitions'>" . lang('Inherited by') . "</h3>\n";
$partition = driver()->partitionsInfo($TABLE);
if ($partition) {
echo "<p><code class='jush-" . JUSH . "'>BY " . h("$partition[partition_by]($partition[partition])") . "</code>\n";
}
tables_links($inherited);
}

View File

@@ -4,7 +4,7 @@ namespace Adminer;
$status = isset($_GET["status"]);
page_header($status ? lang('Status') : lang('Variables'));
$variables = ($status ? show_status() : show_variables());
$variables = ($status ? adminer()->showStatus() : adminer()->showVariables());
if (!$variables) {
echo "<p class='message'>" . lang('No rows.') . "\n";
} else {

View File

@@ -2,6 +2,7 @@
<?php
include __DIR__ . "/adminer/include/version.inc.php";
include __DIR__ . "/adminer/include/errors.inc.php";
include __DIR__ . "/externals/JsShrink/jsShrink.php";
include __DIR__ . "/externals/PhpShrink/phpShrink.php";
function add_apo_slashes($s) {
@@ -46,7 +47,7 @@ function put_file($match) {
// check function definition in drivers
if ($vendor != "mysql") {
preg_match_all(
'~\bfunction ([^(]+)~',
'~\bfunction (?!alter_table|drop_tables|truncate_tables)([^(]+)~', // used for feature detection
preg_replace('~class Driver.*\n\t}~sU', '', file_get_contents(__DIR__ . "/adminer/drivers/mysql.inc.php")),
$matches
); //! respect context (extension, class)
@@ -97,7 +98,7 @@ function put_file($match) {
echo "lang() not found\n";
}
} else {
$return = preg_replace('~// not used in a single language version from here\n.*~s', '', $return);
$return = preg_replace('~// not used in a single language version from here.*~s', '', $return);
$return = preg_replace_callback('~(\$pos = (.+\n).+;)~sU', function ($match) {
return "\$pos = $match[2]\t\t\t: " . (preg_match("~'$_SESSION[lang]'.* \\? (.+)\n~U", $match[1], $match2) ? $match2[1] : "1") . "\n\t\t);";
}, $return);
@@ -164,7 +165,7 @@ function put_file_lang($match) {
case "' . $lang . '": $compressed = "' . add_quo_slashes(lzw_compress(implode("\n", $translation_ids))) . '"; break;';
}
$translations_version = crc32($return);
return 'Lang::$translations = $_SESSION["translations"];
return 'Lang::$translations = (array) $_SESSION["translations"];
if ($_SESSION["translations_version"] != LANG . ' . $translations_version . ') {
Lang::$translations = array();
$_SESSION["translations_version"] = LANG . ' . $translations_version . ';
@@ -198,16 +199,19 @@ function minify_css($file) {
}
function minify_js($file) {
file_put_contents("compile.js", $file);
$terser = shell_exec("terser -c --comments false compile.js"); // prints warning to stderr if terser is not available
if ($terser) {
$file = $terser;
$file = preg_replace_callback("~'use strict';~", function ($match) {
static $count = 0;
$count++;
return ($count == 1 ? $match[0] : ''); // keep only the first one
}, $file);
if (function_exists('jsShrink')) {
$file = jsShrink($file);
}
unlink("compile.js");
return lzw_compress($file);
}
function compile_file($match, $callback = '') { // $callback only to match signature
// $callback only to match signature
function compile_file($match, $callback = '') {
global $project;
$file = "";
list(, $filenames, $callback) = $match;
@@ -300,7 +304,12 @@ if ($vendor) {
}
}
if ($project != "editor" && count(Adminer\SqlDriver::$drivers) == 1) {
$file = str_replace('html_select("auth[driver]", SqlDriver::$drivers, DRIVER, "loginDriver(this);")', 'input_hidden("auth[driver]", "' . ($vendor == "mysql" ? "server" : $vendor) . '") . "' . reset(Adminer\SqlDriver::$drivers) . '"', $file, $count);
$file = str_replace(
'html_select("auth[driver]", SqlDriver::$drivers, DRIVER, "loginDriver(this);")',
'input_hidden("auth[driver]", "' . ($vendor == "mysql" ? "server" : $vendor) . '") . "' . reset(Adminer\SqlDriver::$drivers) . '"',
$file,
$count
);
if (!$count) {
echo "auth[driver] form field not found\n";
}
@@ -321,12 +330,12 @@ if ($project == "editor") {
$file = preg_replace('~;.\.\/externals/jush/jush(-dark)?\.css~', '', $file);
$file = preg_replace('~compile_file\(\'\.\./(externals/jush/modules/jush\.js)[^)]+\)~', "''", $file);
}
$file = preg_replace_callback("~lang\\('((?:[^\\\\']+|\\\\.)*)'([,)])~s", 'lang_ids', $file);
$file = preg_replace_callback("~(?<!>)lang\\('((?:[^\\\\']+|\\\\.)*)'([,)])~s", 'lang_ids', $file);
$file = preg_replace_callback('~\b(include|require) "([^"]*" . LANG . ".inc.php)";~', 'put_file_lang', $file);
$file = str_replace("\r", "", $file);
if ($_SESSION["lang"]) {
// single language version
$file = preg_replace_callback("~(<\\?php\\s*echo )?lang\\('((?:[^\\\\']+|\\\\.)*)'([,)])(;\\s*\\?>)?~s", 'remove_lang', $file);
$file = preg_replace_callback("~(<\\?php\\s*echo )?(?<!>)lang\\('((?:[^\\\\']+|\\\\.)*)'([,)])(;\\s*\\?>)?~s", 'remove_lang', $file);
$file = str_replace("switch_lang();", "", $file);
$file = str_replace('<?php echo LANG; ?>', $_SESSION["lang"], $file);
}

View File

@@ -33,7 +33,11 @@
"php": ">=7.4"
},
"scripts": {
"clean": "rm -f adminer*.php editor*.php",
"compile": "@php compile.php"
"check": [
"phpcs",
"phpstan analyse -c phpstan.neon"
],
"compile": "@php compile.php",
"clean": "rm -f adminer*.php editor*.php"
}
}

View File

@@ -64,7 +64,7 @@ outline:0;background:url(//www.bradezone.com/random/adminer_logo.gif) no-repeat;
* html #lang{padding-top:10px;height:30px}
* html form#form{height:100%}
#logins a,#tables a{background: none}
.logout{color:#fff}
.logout{color:#fff;background-color:#333;box-shadow:0 0 5px 5px #333;z-index:1}
#logout{color:#333;text-decoration:none;border-bottom:1px dotted}
#logout:hover{border-color:#333;background:#333;color:#fff}
.js .column{background:#ddd}

View File

@@ -240,6 +240,8 @@ th {
.logout {
z-index: 2;
background-color: #f2eee1;
box-shadow: 0 0 5px 5px #f2eee1;
}
.js .column {

View File

@@ -0,0 +1,2 @@
## Screenshot
![screenshot](https://www.adminer.org/static/designs/lavender-light/screenshot.png)

View File

@@ -0,0 +1,291 @@
/**
* lavender-light theme for Adminer
* by Alex Yu
*
* Color palette from:
* https://color.adobe.com/Lavender-v2-color-theme-294b8aa6-1935-401f-a763-13b98ec68b90/
*
* - white
* - ghostwhite (f8f8ff)
* - lavender (f0f0ff)
* - darkslateblue (483d8b)
* - aliceblue (f0f8ff)
* - gray (313b43), (21282e)
* - blue (001dff), (002799)
*/
body {
display: grid;
column-gap: 1rem;
row-gap: 20px;
width: 100%;
height: auto;
grid-template-areas:
"menu content";
grid-template-rows: 1fr;
grid-template-columns: 21rem 1fr;
align-items: stretch;
justify-items: start;
font: 0.96rem/1.2rem sans-serif !important;
}
a {
color: #001dff;
text-underline-offset: 1px;
transition-property: all;
transition-timing-function: cubic-bezier(.4,0,.2,1);
transition-duration: .15s;
}
/*
* Basic tags
*/
a:visited {
color: #002799;
}
a:link:hover, a:visited:hover {
color: #7388c9;
text-underline-offset: 4px;
}
h1 {
border: 0;
}
h2 {
margin: 0 0 20px 0px;
line-height: 1.68rem;
border-bottom: 1px solid #20165a; /* darkslateblue; */
background: #3c3085;
color: white;
}
#h1 {
font-size: 1.2rem;
font-style: normal;
font-weight: bold;
text-transform: uppercase;
color: #333;
}
#h1 img {
display: none;
}
/*
* Tables
*/
table {
border-left: 1px solid silver;
}
#table .column {
display: none;
}
td, th {
font-size: 1.05em;
border-right: 1px solid silver;
border-bottom: 1px solid silver;
padding: .3em .6em;
background: none;
}
thead th, thead td {
background: #313b43;
color: white;
border-right: 1px solid #21282e;
border-bottom: 1px solid #21282e;
padding: .5em .5em;
}
thead th a, thead td a,
thead th a:visited, thead td a:visited,
thead th a:hover, thead td a:hover {
color: #eee;
}
th span.column a.text {
color: #2980b9;
}
.js span.column {
background: white;
}
table#table thead .checked td,
table#table thead .checked th {
background: #313b43;
}
.pages {
border: none;
box-shadow: -1px -1px 4px silver;
}
/*
* Common sections
*/
#breadcrumb {
position: static;
top: initial;
left: initial;
margin: 0;
padding: 0.6rem 0 0.4rem 0;
color: #596167;
background: none;
}
#breadcrumb a {
color: #002799;
}
#content {
grid-area: content;
margin: 0;
padding: 0 20px 0 0;
width: calc(100% - 20px);
}
#logout {
font-weight: bold;
text-transform: uppercase;
padding: 0.3rem 0.7rem;
}
#menu {
grid-area: menu;
position: initial;
margin: 0;
padding: 1rem 1.5rem;
width: 18rem;
background: ghostwhite;
overflow-y: auto;
}
#menu h1 {
padding: 0 0 1rem 0;
background: none;
}
#menu #dbs {
padding: 3rem 0 1.5rem;
}
#menu p, #tables {
padding: .8em 0em 1.2rem;
}
#menu #tables a[href*="&select="] {
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHISURBVDjLpVPNK0RRFP+9D98syMwUspHkm9I0YkFZWBFKkZ0s7a3Ewh+ilChK7FgoZCJFKYlYKB8zk2+Z5t0P577He29kQU7dd+6575zf+d1zztWklPiPmOozt/U4SThjXIoyIQS4AJjSXO0lGGlvcXAm6Vzsz4xUhm0AIeX4QLig+C+ZpxbOG1wGhGYHr1zMUmZGWRgs0ha3PE1nX/8mWmdgWTzLB+DUYbhm9FfZ35IEyrhXA3VXJfPbsV8B9LQUIeUHYJ8ASobag1jcucNgW8g9W4reYSDi2YnnZDoDiwCokDANct6NwTB0LEdj0HRA/wxa2SN25JNBEdWluUhZ366gqmAaGvrCAXKOozccTGPgt8+vn8GYSGcgyTYp3dpBnBg42nbQPRBTo5bTvqYkmxL6AQhNTWQGBXY3B7BxlEBXozcW64dxRKoKUZBju+P06gl5WaaviMJBM3TNDlbypemIZgHYOnlwASsCmW7nHADGnBoQ3c76YmweJ9BR5zFYjsbRHwm4tmJg6PhWA7pCXXk+bu7fURHKweXtq/sWaksz7SC/CCGFrwtyZ3r+rCnFRZ7qr1qc6mLZj4f9OEyPL8lVpbX/PucPv5QPKHB1TdEAAAAASUVORK5CYII=) no-repeat scroll right bottom;
display: inline-block;
height: 16px;
margin-right: 4px;
vertical-align: middle;
overflow: hidden;
padding-left: 18px;
border-left: 1px solid transparent;
width: 0;
}
#menu #tables a.active {
border-left: 1px solid #20165a;
}
#menu .links {
display: inline-flex;
flex-direction: column;
flex-wrap: wrap;
padding: 1rem 0 0 1.6rem;
border: 0;
}
#menu #tables li {
list-style: none;
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
align-items: stretch;
flex-direction: row;
}
#menu #tables li a+a {
padding-left: 0.3rem;
}
#lang {
position: absolute;
top: 4rem;
z-index: 100;
padding: 0 0 0 1.5rem;
}
#logins a, #tables a, #tables span {
background: initial;
}
#logins ul {
padding: 3rem 0 1rem;
}
#logins ul li {
margin-bottom: .5rem;
}
/*
* Elements
*/
sup {
padding: 3px 7px;
background: #3498db;
color: white;
border-radius: 2em;
}
code.jush-sql {
display: block;
padding: .4em .7em;
line-height: 1.5em;
}
.jush-bac, .jush-php_bac, .jush-bra, .jush-mssql_bra, .jush-sqlite_quo {
color: #dc33f9;
}
pre, textarea {
padding: 1.5rem;
font: 100% / 1.25 monospace;
}
pre, code {
background: #f2f2ff;
}
pre.sqlarea {
padding: 1rem !important;
border: 2px solid #a9a9a9 !important;
border-radius: 4px;
background: #fff;
}
a.jush-custom:link, a.jush-custom:visited {
color: midnightblue;
}
select, input {
padding: 2px;
}

View File

@@ -16,33 +16,62 @@ Icons from http://FlatIcon.com:
Background adapted from "All Work and No Play", http://thenewcode.com/1008/SVG-Movie-Backgrounds-Andys-Room-and-Overlook-Hotel
*/
html {
--bg: #41658a;
--bleu: #41658a;
--lahtbleu: var(--bleu);
--poiple: #414073;
--lahtpoiple: var(--poiple);
--oringe: #ec5f12;
--oringe-c-fru: rgba(236, 95, 18, 0.6);
--pail-oringe: #f39561;
--menu-w: 20em;
--icon-w: 2em;
--bg: var(--bleu);
--inv-fg: #fff;
--shado: var(--fg);
height: 100vh;
}
html {
height: 100%;
:focus {
outline: thin solid var(--oringe-c-fru);
}
@media (prefers-color-scheme: dark) {
html {
--bleu: #314c68;
--lahtbleu: #649dd6;
--lahtpoiple: #706fc7;
--inv-fg: #eee;
--pail-oringe: #c66734;
--fg: var(--inv-fg);
--dim: #111;
--shado: var(--dim);
}
.jush {
--text-color: #f0f0f0;
}
pre.jush {
background-color: var(--dim);
}
}
body {
width: 100%;
min-height: 100%;
width: 100vw;
min-height: 100vh;
display: flex;
background: #41658a;
align-items: stretch;
box-sizing: border-box;
}
p {
margin-right: 0;
}
a {
color: #41658a;
color: var(--lahtbleu);
}
a:visited {
color: #414073;
color: var(--lahtpoiple);
}
a:link:hover,
a:visited:hover,
a:link:focus,
a:visited:focus {
color: #ec5f12;
color: var(--oringe);
text-decoration: underline;
outline: none;
}
@@ -59,11 +88,18 @@ textarea,
fieldset {
border: thin solid rgba(65, 101, 138, 0.3);
}
@media (prefers-color-scheme: dark) {
input:not([type="image"]),
select,
textarea {
background: #333;
color: #eee;
}
}
label {
white-space: nowrap;
}
.sqlarea {
background: #fff;
border: thin solid rgba(65, 101, 138, 0.3) !important;
width: auto !important;
}
@@ -80,7 +116,7 @@ input[type="button"] {
.error,
.message {
margin-right: 0;
color: #fff;
color: var(--inv-fg);
}
.error,
.error b {
@@ -99,7 +135,9 @@ input[type="button"] {
.message .time {
color: #e7ffaf;
}
thead a sup,
.js .checkable .checked a,
thead a,
thead th a,
.error > a,
.error div > a,
.error p > a,
@@ -108,10 +146,10 @@ thead a sup,
.message p > a {
color: #cce2f8;
}
thead a:link:hover,
thead a:visited:hover,
thead a:link:focus,
thead a:visited:focus,
thead a:hover,
thead a:focus,
thead th a:hover,
thead th a:focus,
.error > a:link:hover,
.error > a:visited:hover,
.error > a:link:focus,
@@ -136,11 +174,17 @@ thead a:visited:focus,
.message p > a:visited:hover,
.message p > a:link:focus,
.message p > a:visited:focus {
color: #f39561;
color: var(--pail-oringe);
}
thead a sup {
color: inherit;
}
pre {
overflow-x: auto;
}
code {
background: var(--dim);
}
code.jush-sql {
display: inline-block;
padding: 0.3em 0.5em 0.2em;
@@ -152,21 +196,21 @@ th > code {
background: transparent;
}
.version {
color: #fff;
color: inherit;
white-space: nowrap;
}
#content,
#menu,
.rtl #content,
.rtl #menu {
margin: 0;
margin: 0 !important;
padding: 0 20px 1.5em;
box-sizing: border-box;
}
#content {
order: 2;
flex: 1 1 auto;
max-width: calc(100% - 20em);
flex: 0 0 auto;
width: calc(100vw - var(--menu-w));
}
#content,
.footer {
@@ -186,12 +230,12 @@ h2,
position: sticky;
top: 0;
z-index: 1;
color: var(--inv-fg);
}
#breadcrumb {
z-index: 2;
white-space: normal;
background: #70a37f;
color: #fff;
padding: 0.1em 2.5em 0.1em 20px;
height: auto;
margin: 0 -20px -2em;
@@ -212,9 +256,9 @@ thead a:visited,
}
h1,
h2 {
padding: 2em 20px 0.5em;
padding: var(--icon-w) 20px 0.5em;
border-bottom-style: none;
color: #fff;
color: var(--inv-fg);
overflow-wrap: break-word;
}
h1,
@@ -233,12 +277,28 @@ h2 a {
h2 {
background: #79b473;
}
@media (prefers-color-scheme: dark) {
#content,
.footer {
background-color: var(--dim);
}
#breadcrumb {
background: #547a5f;
}
h2 {
background: #5f8c59;
}
}
h2 + *,
h2 + .hidden + *,
h2 + * > :first-child,
h2 + .hidden + * > :first-child {
h2 + * > :first-child:not(fieldset),
h2 + .hidden + * > :first-child:not(fieldset) {
margin-top: 0;
}
h2 + form:has( > fieldset:first-child),
h2 + .hidden + form:has( > fieldset:first-child) {
margin-top: -0.8em;
}
h3 {
font-size: 110%;
font-weight: bold;
@@ -251,14 +311,17 @@ fieldset {
margin-left: 0.5em;
}
input.default {
background-color: #414073;
background-color: var(--poiple);
box-shadow: none;
}
input.required {
outline: thin dashed #ec5f12;
outline: thin dashed var(--oringe);
outline-offset: 1px;
box-shadow: none;
}
.odds tbody tr:nth-child(2n) {
background: transparent;
}
table {
border-style: none;
background: rgba(255, 255, 255, 0.6);
@@ -287,9 +350,13 @@ th:last-child {
}
thead th,
thead td {
border-color: #fff;
border-color: var(--inv-fg);
padding: 0.5em 0.8em 0.6em;
color: #fff;
}
.js .checkable thead .checked th,
thead th,
thead td {
color: var(--inv-fg);
}
thead th {
text-align: left;
@@ -297,7 +364,7 @@ thead th {
}
.js .checkable thead .checked th,
thead th {
background: #414073;
background: var(--poiple);
position: relative;
background-clip: padding-box;
}
@@ -306,14 +373,15 @@ thead th {
}
.js .checkable thead .checked td,
thead td {
background: #41658a;
background: var(--bleu);
}
.js .column {
z-index: 1;
background: transparent;
padding: 0;
margin-top: 0;
line-height: 1.25em
line-height: 1.25em;
border: none;
}
.column a {
margin-left: 0.2em;
@@ -332,7 +400,7 @@ thead td {
}
.column a:hover,
.column a:focus {
background-color: #ec5f12;
background-color: var(--oringe);
}
tbody tr:nth-child(even) td {
background: rgba(65, 101, 138, 0.06);
@@ -346,6 +414,13 @@ tbody tr:nth-child(n):hover td {
tbody tr:nth-child(n):hover th {
background: rgba(236, 72, 18, 0.2);
}
.js .checkable tbody .checked td,
.js .checkable tbody .checked th {
color: var(--fg);
}
.js .checkable tbody .checked td code {
background: rgba(0, 0, 0, 0.25);
}
.js .checkable tbody .checked td {
background: rgba(236, 72, 18, 0.25);
}
@@ -362,27 +437,47 @@ tbody tr:nth-child(n):hover th {
.js .checkable tbody .checked:hover th {
background: rgba(236, 72, 18, 0.45);
}
@media (prefers-color-scheme: dark) {
table {
background: rgba(255, 255, 255, 0.02);
}
td,
th {
border-color: #494c4f;
}
thead th,
thead td {
border-color: #444;
}
tbody tr:nth-child(even) td {
background: rgba(65, 101, 138, 0.09);
}
tbody tr:nth-child(even) th {
background: rgba(65, 64, 115, 0.09);
}
}
.icon {
width: 1.2em;
background-color: #4c3957;
background-size: 66%;
filter: none;
}
.icon-plus {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' version='1.1' x='0px' y='0px' width='512px' height='512px' viewBox='0 0 456 456' style='enable-background:new 0 0 456 456;'%3E%3Cg%3E%3Cpolygon points='456,157.566 298.433,157.566 298.433,0 157.567,0 157.567,157.566 0,157.566 0,298.434 157.567,298.434 157.567,456 298.433,456 298.433,298.434 456,298.434' fill='%23FFFFFF'/%3E%3C/g%3E%3C/svg%3E");
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' version='1.1' width='512px' height='512px' viewBox='0 0 456 456'%3E%3Cg%3E%3Cpolygon points='456,157.566 298.433,157.566 298.433,0 157.567,0 157.567,157.566 0,157.566 0,298.434 157.567,298.434 157.567,456 298.433,456 298.433,298.434 456,298.434' fill='%23FFFFFF'/%3E%3C/g%3E%3C/svg%3E");
}
.column a[href*="&asc%5B"],
.icon-up {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' version='1.1' x='0px' y='0px' viewBox='0 0 490 490' style='enable-background:new 0 0 490 490;' width='512px' height='512px'%3E%3Cg%3E%3Cpath d='M490,474.459H0L245.009,15.541L490,474.459z' fill='%23FFFFFF'/%3E%3C/g%3E%3C/svg%3E");
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' version='1.1' viewBox='0 0 490 490' width='512px' height='512px'%3E%3Cg%3E%3Cpath d='M490,474.459H0L245.009,15.541L490,474.459z' fill='%23FFFFFF'/%3E%3C/g%3E%3C/svg%3E");
}
.column a[href*="&desc%5B"],
.icon-down {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' version='1.1' x='0px' y='0px' viewBox='0 0 490 490' style='enable-background:new 0 0 490 490;' width='512px' height='512px'%3E%3Cg%3E%3Cpath d='M0,15.541h490L244.991,474.459L0,15.541z' fill='%23FFFFFF'/%3E%3C/g%3E%3C/svg%3E");
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' version='1.1' viewBox='0 0 490 490' width='512px' height='512px'%3E%3Cg%3E%3Cpath d='M0,15.541h490L244.991,474.459L0,15.541z' fill='%23FFFFFF'/%3E%3C/g%3E%3C/svg%3E");
}
.icon-cross {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' version='1.1' x='0px' y='0px' viewBox='0 0 174.239 174.239' style='enable-background:new 0 0 174.239 174.239;' width='512px' height='512px'%3E%3Cg%3E%3Cpath d='M146.537,1.047c-1.396-1.396-3.681-1.396-5.077,0L89.658,52.849c-1.396,1.396-3.681,1.396-5.077,0L32.78,1.047 c-1.396-1.396-3.681-1.396-5.077,0L1.047,27.702c-1.396,1.396-1.396,3.681,0,5.077l51.802,51.802c1.396,1.396,1.396,3.681,0,5.077 L1.047,141.46c-1.396,1.396-1.396,3.681,0,5.077l26.655,26.655c1.396,1.396,3.681,1.396,5.077,0l51.802-51.802 c1.396-1.396,3.681-1.396,5.077,0l51.801,51.801c1.396,1.396,3.681,1.396,5.077,0l26.655-26.655c1.396-1.396,1.396-3.681,0-5.077 l-51.801-51.801c-1.396-1.396-1.396-3.681,0-5.077l51.801-51.801c1.396-1.396,1.396-3.681,0-5.077L146.537,1.047z' fill='%23FFFFFF'/%3E%3C/g%3E%3C/svg%3E");
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' version='1.1' viewBox='0 0 174.239 174.239' width='512px' height='512px'%3E%3Cg%3E%3Cpath d='M146.537,1.047c-1.396-1.396-3.681-1.396-5.077,0L89.658,52.849c-1.396,1.396-3.681,1.396-5.077,0L32.78,1.047 c-1.396-1.396-3.681-1.396-5.077,0L1.047,27.702c-1.396,1.396-1.396,3.681,0,5.077l51.802,51.802c1.396,1.396,1.396,3.681,0,5.077 L1.047,141.46c-1.396,1.396-1.396,3.681,0,5.077l26.655,26.655c1.396,1.396,3.681,1.396,5.077,0l51.802-51.802 c1.396-1.396,3.681-1.396,5.077,0l51.801,51.801c1.396,1.396,3.681,1.396,5.077,0l26.655-26.655c1.396-1.396,1.396-3.681,0-5.077 l-51.801-51.801c-1.396-1.396-1.396-3.681,0-5.077l51.801-51.801c1.396-1.396,1.396-3.681,0-5.077L146.537,1.047z' fill='%23FFFFFF'/%3E%3C/g%3E%3C/svg%3E");
}
.column a[href="#fieldset-search"] {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' version='1.1' viewBox='0 0 310.088 310.088' enable-background='new 0 0 310.088 310.088' width='512px' height='512px'%3E%3Cg%3E%3Cpath d='m299.85,250.413l-62.808-62.808c-3.982-3.982-10.437-3.982-14.418,0l-3.539,3.539-18.586-18.586c29.709-42.872 25.472-102.152-12.716-140.34-42.958-42.958-112.606-42.958-155.563,0s-42.958,112.606 0,155.563c38.189,38.188 97.468,42.425 140.34,12.716l18.586,18.586-3.539,3.539c-3.982,3.981-3.982,10.437 0,14.418l62.808,62.808c13.651,13.651 35.785,13.651 49.436,0s13.65-35.784-0.001-49.435zm-251.368-78.895c-33.921-33.921-33.921-89.115-0.001-123.036 33.922-33.921 89.117-33.922 123.037-0.001v0.001c33.922,33.921 33.922,89.115 0,123.036-16.96,16.961-39.239,25.441-61.518,25.441-22.279,0-44.558-8.48-61.518-25.441z' fill='%23FFFFFF'/%3E%3C/g%3E%3C/svg%3E");
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' version='1.1' viewBox='0 0 310.088 310.088' width='512px' height='512px'%3E%3Cg%3E%3Cpath d='m299.85,250.413l-62.808-62.808c-3.982-3.982-10.437-3.982-14.418,0l-3.539,3.539-18.586-18.586c29.709-42.872 25.472-102.152-12.716-140.34-42.958-42.958-112.606-42.958-155.563,0s-42.958,112.606 0,155.563c38.189,38.188 97.468,42.425 140.34,12.716l18.586,18.586-3.539,3.539c-3.982,3.981-3.982,10.437 0,14.418l62.808,62.808c13.651,13.651 35.785,13.651 49.436,0s13.65-35.784-0.001-49.435zm-251.368-78.895c-33.921-33.921-33.921-89.115-0.001-123.036 33.922-33.921 89.117-33.922 123.037-0.001v0.001c33.922,33.921 33.922,89.115 0,123.036-16.96,16.961-39.239,25.441-61.518,25.441-22.279,0-44.558-8.48-61.518-25.441z' fill='%23FFFFFF'/%3E%3C/g%3E%3C/svg%3E");
}
.loadmore,
.rtl .loadmore {
@@ -402,26 +497,33 @@ tbody tr:nth-child(n):hover th {
.footer ~ div {
margin-top: 0.8em;
}
.js #menuopen,
#lang,
.logout {
z-index: 3;
margin: 0;
padding: 0;
overflow: hidden;
width: 2em;
height: 2em;
width: var(--icon-w);
height: var(--icon-w);
box-shadow: none;
}
#lang,
.logout {
position: fixed;
}
#lang {
top: 0;
left: 18em;
--lang-offset: calc(var(--menu-w) - var(--icon-w));
left: var(--lang-offset);
}
.rtl #lang {
right: 18em;
right: var(--lang-offset);
}
.rtl .logout {
margin: 0;
}
.js #menuopen button,
#lang select,
#logout {
opacity: 0;
@@ -433,8 +535,9 @@ tbody tr:nth-child(n):hover th {
cursor: pointer;
z-index: 1;
}
#lang:before,
.logout:before {
.js #menuopen::before,
#lang label::before,
.logout::before {
position: absolute;
top: 0;
left: 0;
@@ -446,33 +549,70 @@ tbody tr:nth-child(n):hover th {
background: #2d3047 center no-repeat;
background-size: 70%;
}
#lang:before {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' version='1.1' viewBox='0 0 470 470' enable-background='new 0 0 470 470' width='512px' height='512px'%3E%3Cg%3E%3Cpath d='m432.5,227.5h-77.031c-0.611-37.438-5.782-73.616-14.771-105.694h50.518c4.143,0 7.5-3.357 7.5-7.5s-3.357-7.5-7.5-7.5h-55.112c-8.018-24.165-18.316-45.521-30.553-62.656-2.408-3.371-7.093-4.153-10.462-1.745-3.371,2.407-4.152,7.092-1.745,10.462 10.618,14.868 19.688,33.199 26.965,53.939h-77.809v-69.306c0-4.143-3.357-7.5-7.5-7.5s-7.5,3.357-7.5,7.5v69.306h-77.81c7.277-20.74 16.347-39.071 26.965-53.939 2.407-3.37 1.626-8.055-1.745-10.462-3.372-2.407-8.055-1.625-10.462,1.745-12.237,17.135-22.535,38.492-30.553,62.656h-55.112c-4.143,0-7.5,3.357-7.5,7.5s3.357,7.5 7.5,7.5h50.518c-8.988,32.078-14.159,68.256-14.771,105.694h-77.03c-4.143,0-7.5,3.357-7.5,7.5s3.357,7.5 7.5,7.5h77.031c0.611,37.438 5.782,73.616 14.771,105.694h-50.519c-4.143,0-7.5,3.357-7.5,7.5s3.357,7.5 7.5,7.5h55.112c8.019,24.169 18.32,45.529 30.56,62.666 1.464,2.049 3.77,3.142 6.11,3.142 1.508,0 3.031-0.454 4.353-1.397 3.37-2.408 4.151-7.092 1.744-10.463-10.621-14.869-19.693-33.204-26.972-53.947h77.81v69.305c0,4.143 3.357,7.5 7.5,7.5s7.5-3.357 7.5-7.5v-69.306h77.81c-7.278,20.744-16.351,39.078-26.972,53.947-2.407,3.371-1.626,8.055 1.744,10.463 1.321,0.943 2.844,1.397 4.353,1.397 2.341,0 4.646-1.093 6.11-3.142 12.24-17.137 22.54-38.497 30.56-62.666h55.112c4.143,0 7.5-3.357 7.5-7.5s-3.357-7.5-7.5-7.5h-50.519c8.989-32.078 14.16-68.256 14.771-105.694h77.031c4.143,0 7.5-3.357 7.5-7.5s-3.357-7.499-7.5-7.499zm-107.36-105.694c9.313,31.683 14.695,67.958 15.326,105.694h-97.966v-105.694h82.64zm-180.28,0h82.64v105.694h-97.966c0.632-37.737 6.013-74.011 15.326-105.694zm0,226.388c-9.313-31.683-14.695-67.958-15.326-105.694h97.966v105.694h-82.64zm180.28,0h-82.64v-105.694h97.966c-0.632,37.737-6.013,74.012-15.326,105.694z' fill='%23ECEBE4'/%3E%3Cpath d='M401.17,68.83C356.784,24.444,297.771,0,235,0S113.216,24.444,68.83,68.83S0,172.229,0,235.001 c0,46.271,13.391,90.899,38.764,129.316l-28.718,86.148c-0.898,2.695-0.197,5.667,1.812,7.676c2.009,2.008,4.979,2.708,7.676,1.812 l86.15-28.716C144.102,456.609,188.729,470,235,470c62.771,0,121.784-24.444,166.17-68.83S470,297.771,470,235.001 C470,172.229,445.556,113.216,401.17,68.83z M235,455c-44.491,0-87.355-13.222-123.961-38.235 c-1.262-0.862-2.739-1.308-4.231-1.308c-0.797,0-1.598,0.127-2.372,0.385L29.02,440.979l25.14-75.414 c0.741-2.225,0.399-4.668-0.923-6.604C28.222,322.357,15,279.492,15,235.001C15,113.692,113.691,15,235,15s220,98.692,220,220.001 C455,356.309,356.309,455,235,455z' fill='%23ECEBE4'/%3E%3C/g%3E%3C/svg%3E");
#lang label::before {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' version='1.1' viewBox='0 0 470 470' width='512px' height='512px'%3E%3Cg%3E%3Cpath d='m432.5,227.5h-77.031c-0.611-37.438-5.782-73.616-14.771-105.694h50.518c4.143,0 7.5-3.357 7.5-7.5s-3.357-7.5-7.5-7.5h-55.112c-8.018-24.165-18.316-45.521-30.553-62.656-2.408-3.371-7.093-4.153-10.462-1.745-3.371,2.407-4.152,7.092-1.745,10.462 10.618,14.868 19.688,33.199 26.965,53.939h-77.809v-69.306c0-4.143-3.357-7.5-7.5-7.5s-7.5,3.357-7.5,7.5v69.306h-77.81c7.277-20.74 16.347-39.071 26.965-53.939 2.407-3.37 1.626-8.055-1.745-10.462-3.372-2.407-8.055-1.625-10.462,1.745-12.237,17.135-22.535,38.492-30.553,62.656h-55.112c-4.143,0-7.5,3.357-7.5,7.5s3.357,7.5 7.5,7.5h50.518c-8.988,32.078-14.159,68.256-14.771,105.694h-77.03c-4.143,0-7.5,3.357-7.5,7.5s3.357,7.5 7.5,7.5h77.031c0.611,37.438 5.782,73.616 14.771,105.694h-50.519c-4.143,0-7.5,3.357-7.5,7.5s3.357,7.5 7.5,7.5h55.112c8.019,24.169 18.32,45.529 30.56,62.666 1.464,2.049 3.77,3.142 6.11,3.142 1.508,0 3.031-0.454 4.353-1.397 3.37-2.408 4.151-7.092 1.744-10.463-10.621-14.869-19.693-33.204-26.972-53.947h77.81v69.305c0,4.143 3.357,7.5 7.5,7.5s7.5-3.357 7.5-7.5v-69.306h77.81c-7.278,20.744-16.351,39.078-26.972,53.947-2.407,3.371-1.626,8.055 1.744,10.463 1.321,0.943 2.844,1.397 4.353,1.397 2.341,0 4.646-1.093 6.11-3.142 12.24-17.137 22.54-38.497 30.56-62.666h55.112c4.143,0 7.5-3.357 7.5-7.5s-3.357-7.5-7.5-7.5h-50.519c8.989-32.078 14.16-68.256 14.771-105.694h77.031c4.143,0 7.5-3.357 7.5-7.5s-3.357-7.499-7.5-7.499zm-107.36-105.694c9.313,31.683 14.695,67.958 15.326,105.694h-97.966v-105.694h82.64zm-180.28,0h82.64v105.694h-97.966c0.632-37.737 6.013-74.011 15.326-105.694zm0,226.388c-9.313-31.683-14.695-67.958-15.326-105.694h97.966v105.694h-82.64zm180.28,0h-82.64v-105.694h97.966c-0.632,37.737-6.013,74.012-15.326,105.694z' fill='%23ECEBE4'/%3E%3Cpath d='M401.17,68.83C356.784,24.444,297.771,0,235,0S113.216,24.444,68.83,68.83S0,172.229,0,235.001 c0,46.271,13.391,90.899,38.764,129.316l-28.718,86.148c-0.898,2.695-0.197,5.667,1.812,7.676c2.009,2.008,4.979,2.708,7.676,1.812 l86.15-28.716C144.102,456.609,188.729,470,235,470c62.771,0,121.784-24.444,166.17-68.83S470,297.771,470,235.001 C470,172.229,445.556,113.216,401.17,68.83z M235,455c-44.491,0-87.355-13.222-123.961-38.235 c-1.262-0.862-2.739-1.308-4.231-1.308c-0.797,0-1.598,0.127-2.372,0.385L29.02,440.979l25.14-75.414 c0.741-2.225,0.399-4.668-0.923-6.604C28.222,322.357,15,279.492,15,235.001C15,113.692,113.691,15,235,15s220,98.692,220,220.001 C455,356.309,356.309,455,235,455z' fill='%23ECEBE4'/%3E%3C/g%3E%3C/svg%3E");
}
.logout:before {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' version='1.1' viewBox='0 0 512 512' enable-background='new 0 0 512 512' width='512px' height='512px'%3E%3Cg%3E%3Cpath d='m256,501c-129.6,0-235-102.2-235-227.8 0-87.8 50.6-166.3 132.1-204.9 10.2-4.8 22.4-0.5 27.2,9.7 4.8,10.2 0.5,22.4-9.7,27.2-67.1,31.8-108.7,96.1-108.7,168-7.10543e-15,103.1 87.1,187 194.1,187 107,0 194.1-83.9 194.1-187 0-72.4-44-138.9-112.2-169.5-10.3-4.6-14.9-16.7-10.3-27 4.6-10.3 16.7-14.9 27-10.2 82.9,37.1 136.4,118.3 136.4,206.7 0,125.6-105.4,227.8-235,227.8z' fill='%23FFFFFF'/%3E%3Cpath d='m256,287.9c-11.3,0-20.4-9.1-20.4-20.4v-236.1c0-11.3 9.2-20.4 20.4-20.4 11.3,0 20.4,9.1 20.4,20.4v236.1c0,11.3-9.1,20.4-20.4,20.4z' fill='%23FFFFFF'/%3E%3C/g%3E%3C/svg%3E");
.logout::before {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' version='1.1' viewBox='0 0 512 512' width='512px' height='512px'%3E%3Cg%3E%3Cpath d='m256,501c-129.6,0-235-102.2-235-227.8 0-87.8 50.6-166.3 132.1-204.9 10.2-4.8 22.4-0.5 27.2,9.7 4.8,10.2 0.5,22.4-9.7,27.2-67.1,31.8-108.7,96.1-108.7,168-7.10543e-15,103.1 87.1,187 194.1,187 107,0 194.1-83.9 194.1-187 0-72.4-44-138.9-112.2-169.5-10.3-4.6-14.9-16.7-10.3-27 4.6-10.3 16.7-14.9 27-10.2 82.9,37.1 136.4,118.3 136.4,206.7 0,125.6-105.4,227.8-235,227.8z' fill='%23FFFFFF'/%3E%3Cpath d='m256,287.9c-11.3,0-20.4-9.1-20.4-20.4v-236.1c0-11.3 9.2-20.4 20.4-20.4 11.3,0 20.4,9.1 20.4,20.4v236.1c0,11.3-9.1,20.4-20.4,20.4z' fill='%23FFFFFF'/%3E%3C/g%3E%3C/svg%3E");
}
#lang:focus-within:before,
#lang:hover:before,
.logout:focus-within:before,
.logout:hover:before {
background-color: #ec5f12;
.js #menuopen::before {
background-image: url("data:image/svg+xml,%3Csvg version='1.1' baseProfile='full' xmlns='http://www.w3.org/2000/svg' width='120' height='120' viewBox='0 0 120 120'%3E%3Cdefs%3E%3Cpath id='bar' fill='%23FFFFFF' d='m87,-3 a3 3 180 0 1 0,6 h-84 a3 3 180 0 1 0,-6 z'/%3E%3C/defs%3E%3Cuse href='%23bar' x='15' y='18'/%3E%3Cuse href='%23bar' x='15' y='60'/%3E%3Cuse href='%23bar' x='15' y='102'/%3E%3C/svg%3E");
}
.js #menuopen:focus-within::before,
.js #menuopen:hover::before,
#lang label:focus-within::before,
#lang label:hover::before,
.logout:focus-within::before,
.logout:hover::before {
background-color: var(--oringe);
}
.foot,
#menu {
display: flex;
flex-direction: column;
align-items: stretch;
}
#foot {
position: relative;
flex: 0 0 auto;
width: var(--menu-w);
}
#menu {
position: relative;
top: 0;
width: auto;
flex: 0 0 20em;
max-width: 20em;
z-index: 1;
position: static;
width: 100%;
flex-grow: 1;
}
h1 {
background: #414073;
background: var(--poiple);
margin-bottom: 0;
}
#h1 {
font-style: normal;
}
#dbs {
display: flex;
align-items: center;
gap: 0.5em;
color: transparent;
}
#dbs label {
flex-grow: 1;
display: flex;
align-items: center;
}
#dbs label::before {
content: " ";
display: inline-block;
vertical-align: middle;
height: 1em;
width: 1em;
margin-inline-end: -1em;
background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' version='1.1' viewBox='0 0 58.201 58.201' width='512px' height='512px'%3E%3Cg%3E%3Cpath d='M31.707,33.07c-0.87,0.027-1.74,0.042-2.606,0.042c-0.869,0-1.742-0.014-2.614-0.042 c-7.341-0.201-13.191-1.238-17.403-2.717C7.104,29.685,5.409,28.899,4.1,28v7.111v0.5v0.5V37.4c2.846,2.971,12.394,5.711,25,5.711 s22.154-2.74,25-5.711v-1.289v-0.5v-0.5V28c-1.318,0.905-3.028,1.697-5.025,2.367C44.865,31.839,39.027,32.87,31.707,33.07z' fill='%23FFFFFF'/%3E%3Cpath d='M4.1,14.889V22v0.5V23v1.289c2.638,2.754,11.033,5.31,22.286,5.668c0.115,0.004,0.233,0.005,0.349,0.008 c0.326,0.009,0.651,0.018,0.982,0.023C28.174,29.996,28.635,30,29.1,30s0.926-0.004,1.383-0.011 c0.33-0.005,0.656-0.014,0.982-0.023c0.116-0.003,0.234-0.005,0.349-0.008c11.253-0.359,19.648-2.915,22.286-5.668V23v-0.5V22 v-7.111C49.233,18.232,38.944,20,29.1,20S8.968,18.232,4.1,14.889z' fill='%23FFFFFF'/%3E%3Cpath d='M53.965,8.542C52.843,4.241,44.215,0,29.1,0C14.023,0,5.404,4.22,4.247,8.51C4.162,8.657,4.1,8.818,4.1,9v0.5v1.806 C6.937,14.267,16.417,17,29.1,17s22.164-2.733,25-5.694V9.5V9C54.1,8.832,54.044,8.681,53.965,8.542z' fill='%23FFFFFF'/%3E%3Cpath d='M4.1,41v8.201c0,0.162,0.043,0.315,0.117,0.451c1.181,4.895,11.747,8.549,24.883,8.549c13.106,0,23.655-3.639,24.875-8.516 c0.08-0.144,0.125-0.309,0.125-0.484v-8.199c-4.135,2.911-12.655,5.199-25,5.199C16.754,46.201,8.234,43.911,4.1,41z' fill='%23FFFFFF'/%3E%3C/g%3E%3C/svg%3E") center no-repeat;
background-size: auto 100%;
}
#dbs select {
flex-grow: 1;
}
#menu p {
margin: 1.5em 0 0;
}
@@ -486,6 +626,13 @@ h1 {
#menu .error {
padding: 0.2em 0.4em;
}
p:has(#filter-field) {
display: flex;
}
#filter-field {
flex: 1 1 auto;
max-width: 100%;
}
.tables-filter {
padding: 0;
margin-top: 1.2em;
@@ -499,7 +646,7 @@ button,
font-size: 85%;
text-align: center;
background-color: #4c3957;
color: #fff;
color: var(--inv-fg);
padding: 0.5em 0.8em 0.6em;
margin: 0;
border-style: none;
@@ -516,9 +663,11 @@ button:focus,
.links a:link:hover,
.links a:visited:hover,
.links a:link:focus,
.links a:visited:focus {
color: #fff;
background-color: #ec5f12;
.links a:visited:focus,
#menu .links a:hover,
#menu .links a:focus {
color: var(--inv-fg);
background-color: var(--oringe);
text-decoration: none;
}
input[type="submit"]:disabled,
@@ -533,7 +682,7 @@ input[type="file"]::-ms-browse {
font-size: 85%;
text-align: center;
background: #4c3957;
color: #fff;
color: var(--inv-fg);
padding: 0.5em 0.8em 0.6em;
margin: 0 0.5em;
border-style: none;
@@ -541,8 +690,8 @@ input[type="file"]::-ms-browse {
}
input[type="file"]:hover::-ms-browse,
input[type="file"]:focus::-ms-browse {
color: #fff;
background: #ec5f12;
color: var(--inv-fg);
background: var(--oringe);
text-decoration: none;
}
input[type="file"]:disabled::-ms-browse {
@@ -555,7 +704,7 @@ input[type="file"]::-webkit-file-upload-button {
font-size: 85%;
text-align: center;
background: #4c3957;
color: #fff;
color: var(--inv-fg);
padding: 0.5em 0.8em 0.6em;
margin: 0 0.5em;
border-style: none;
@@ -563,8 +712,8 @@ input[type="file"]::-webkit-file-upload-button {
}
input[type="file"]:hover::-webkit-file-upload-button,
input[type="file"]:focus::-webkit-file-upload-button {
color: #fff;
background: #ec5f12;
color: var(--inv-fg);
background: var(--oringe);
text-decoration: none;
}
input[type="file"]:disabled::-webkit-file-upload-button {
@@ -576,7 +725,7 @@ input[type="file"]::file-selector-button {
font-size: 85%;
text-align: center;
background: #4c3957;
color: #fff;
color: var(--inv-fg);
padding: 0.5em 0.8em 0.6em;
margin: 0 0.5em;
border-style: none;
@@ -584,24 +733,25 @@ input[type="file"]::file-selector-button {
}
input[type="file"]:hover::file-selector-button,
input[type="file"]:focus::file-selector-button {
color: #fff;
background: #ec5f12;
color: var(--inv-fg);
background: var(--oringe);
text-decoration: none;
}
input[type="file"]:disabled::file-selector-button {
background-color: rgba(76, 57, 87, 0.35);
cursor: not-allowed;
}
.links .active {
#menu .active {
color: var(--inv-fg);
font-weight: normal;
background-color: #414073;
background-color: var(--poiple);
}
#menu .links {
display: flex;
flex-wrap: wrap;
margin: 1em -5px -5px;
}
#menu .links:after {
#menu .links::after {
content: " ";
display: table;
clear: both;
@@ -612,23 +762,26 @@ input[type="file"]:disabled::file-selector-button {
}
#logins,
#tables {
flex-grow: 1;
margin: 0.7em -20px -20px;
padding: 0 20px 20px;
overflow: hidden !important;
}
#logins:focus-within,
#tables:focus-within,
#logins:hover,
#tables:hover {
overflow: visible !important;
}
#logins li,
#tables li {
background: #41658a;
background: var(--bleu);
}
#logins a,
#tables a,
#tables span {
background: #41658a;
color: #fff;
background: var(--bleu);
color: var(--inv-fg);
padding: 0.2em 0.4em 0.3em 0;
}
.rtl #logins a,
@@ -648,18 +801,14 @@ input[type="file"]:disabled::file-selector-button {
overflow: hidden;
background: transparent;
color: transparent;
margin-left: -0.2em;
margin-inline-start: -0.2em;
white-space: nowrap;
padding: 0.1em 0.2em;
top: 0.4em;
}
.rtl #tables a.select {
margin-left: 0;
margin-right: -0.2em;
}
#tables a.select:before {
#tables a.select::before {
content: ' ';
background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' version='1.1' width='512px' height='512px' viewBox='0 0 16 16'%3E%3Cg%3E%3Cpath fill='%23FFFFFF' d='M0 1v15h16v-15h-16zM5 15h-4v-2h4v2zM5 12h-4v-2h4v2zM5 9h-4v-2h4v2zM5 6h-4v-2h4v2zM10 15h-4v-2h4v2zM10 12h-4v-2h4v2zM10 9h-4v-2h4v2zM10 6h-4v-2h4v2zM15 15h-4v-2h4v2zM15 12h-4v-2h4v2zM15 9h-4v-2h4v2zM15 6h-4v-2h4v2z'/%3E%3C/g%3E%3C/svg%3E") center no-repeat;
background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' version='1.1' width='512px' height='512px' viewBox='0 0 16 16'%3E%3Cg%3E%3Cpath fill='%23FFFFFF' d='M0 1v15h16v-15h-16zM5 15h-4v-2h4v2zM5 12h-4v-2h4v2zM5 9h-4v-2h4v2zM5 6h-4v-2h4v2zM10 15h-4v-2h4v2zM10 12h-4v-2h4v2zM10 9h-4v-2h4v2zM10 6h-4v-2h4v2zM15 15h-4v-2h4v2zM15 12h-4v-2h4v2zM15 9h-4v-2h4v2zM15 6h-4v-2h4v2z'/%3E%3C/g%3E%3C/svg%3E") center no-repeat;
background-size: contain;
display: block;
height: 100%;
@@ -667,23 +816,16 @@ input[type="file"]:disabled::file-selector-button {
#tables a.select:hover,
#tables a.select:focus,
#tables a.select.active {
background-color: #ec5f12;
background-color: var(--oringe);
}
#tables a.select.active ~ .structure {
font-weight: bold;
}
#routines + .links a {
margin-right: 0.45em;
margin-inline-end: 0.45em;
}
#routines + .links a:last-child {
margin-right: 0;
}
.rtl #routines + .links a {
margin-right: 0;
margin-left: 0.45em;
}
.rtl #routines + .links a:last-child {
margin-left: 0;
margin-inline-end: 0;
}
.rtl p,
.rtl table,
@@ -693,11 +835,22 @@ input[type="file"]:disabled::file-selector-button {
}
@media all and (max-width: 800px) {
body {
.js body {
padding-bottom: 2em;
}
.js #menuopen {
left: 14px;
top: 0;
left: 0;
width: var(--icon-w);
height: var(--icon-w);
}
.rtl.js #menuopen {
left: auto;
right: 0;
}
.js #menu {
border: none;
box-shadow: 0 0 10px 2px var(--shado);
}
body,
#content,
@@ -711,24 +864,33 @@ input[type="file"]:disabled::file-selector-button {
#menu {
max-width: none;
padding: 0 10px 2em;
width: auto;
width: 100vw;
}
#foot {
width: 100vw;
}
.rtl #breadcrumb,
#breadcrumb {
padding: 0 10px;
margin: 0 -10px;
}
#breadcrumb {
padding: 0 28px;
height: 2em;
line-height: 2em;
margin: 0 -10px;
overflow: auto;
position: static;
white-space: nowrap;
width: 100%;
width: 100vw;
box-sizing: border-box;
}
.rtl #breadcrumb {
padding: 0 10px 0 0;
margin: 0 -10px;
.js #breadcrumb {
padding-left: calc(10px + var(--icon-w));
}
#breadcrumb:after {
.rtl.js #breadcrumb {
padding-left: 10px;
padding-right: calc(10px + var(--icon-w));
}
#breadcrumb::after {
content: '';
display: inline-block;
width: 2.4em;
@@ -741,6 +903,12 @@ input[type="file"]:disabled::file-selector-button {
margin: 0 -10px 1em;
padding: 1em 10px 0.5em;
}
.js #menuopen ~ h2 {
padding-top: var(--icon-w);
}
.js #menuopen ~ #breadcrumb ~ h2 {
padding-top: 1em;
}
#content .links a {
white-space: normal;
margin-bottom: 0.2em;
@@ -751,16 +919,19 @@ input[type="file"]:disabled::file-selector-button {
.logout {
position: absolute;
}
.js .logout {
top: 0;
box-shadow: none;
}
#lang {
margin-left: auto;
margin: -1.5em -10px 0 auto;
position: relative;
top: auto;
left: auto;
margin-bottom: -2em;
}
.rtl #lang {
right: auto;
margin-left: 0;
margin-left: -10px;
margin-right: auto;
}
#logins,

View File

@@ -1077,6 +1077,10 @@ body > form{
/*position: relative;*/
}
.logout{
z-index: 5000;
}
#lang {
z-index: 5000;
position: absolute;

View File

@@ -413,6 +413,8 @@ thead td abbr, thead td sup, thead th acronym, thead th sup {
.logout {
top: 28px;
background-color: #31587d;
box-shadow: 0 0 3px 3px #31587d;
}
.js .column {

View File

@@ -558,6 +558,8 @@ input[name="delete"]:hover, input[name="drop"]:hover {
.logout {
color: #fff;
background-color: #333;
box-shadow: 0 0 4px 4px #333;
margin-top: 0.7em;
}

View File

@@ -238,6 +238,8 @@ th {
.logout {
z-index: 5;
background-color: #f2eee1;
box-shadow: 0 0 4px 4px #f2eee1;
}
.js .column {

View File

@@ -318,6 +318,8 @@ input[name=logout]:hover {
.logout {
position: fixed;
z-index:3;
background-color: #48A5BF;
box-shadow: 0 0 4px 4px #48A5BF;
}
.logout form {

View File

@@ -69,6 +69,7 @@ p.tabs{margin:0 20px 0 0}
fieldset{border:1px solid #999;border-radius:5px;display:inline;margin:.8em .5em 0 0;padding:.5em .8em;vertical-align:top}
a{color:#369}
a:hover{color:#28c}
.logout{background:transparent;box-shadow:none}
#logout,#logins a,#logins a:link,#breadcrumb a,#breadcrumb a:link,.logout a,.logout a:link,.tabs a,.tabs a:link{display:inline-block;border:1px solid #667eac;border-radius:4px;padding:3px 8px;margin:2px 0;vertical-align:middle;text-decoration:none;color:#fff;background:#7abcff;background:-moz-linear-gradient(top,#7abcff 0,#60abf8 44%,#4096ee 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#7abcff),color-stop(44%,#60abf8),color-stop(100%,#4096ee));background:-webkit-linear-gradient(top,#7abcff 0,#60abf8 44%,#4096ee 100%);background:-o-linear-gradient(top,#7abcff 0,#60abf8 44%,#4096ee 100%);background:-ms-linear-gradient(top,#7abcff 0,#60abf8 44%,#4096ee 100%);background:linear-gradient(to bottom,#7abcff 0,#60abf8 44%,#4096ee 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#7abcff',endColorstr='#4096ee',GradientType=0)}
.logout a,.logout a:link,#logout{padding:3px 4px}
#logout:hover,#logins a:hover,#tables a:hover,#breadcrumb a:hover,.logout a:hover,.tabs a:hover{color:#fff;text-decoration:none;background:#93c9ff;background:-moz-linear-gradient(top,#93c9ff 0,#79b8f7 44%,#57a2ed 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#93c9ff),color-stop(44%,#79b8f7),color-stop(100%,#57a2ed));background:-webkit-linear-gradient(top,#93c9ff 0,#79b8f7 44%,#57a2ed 100%);background:-o-linear-gradient(top,#93c9ff 0,#79b8f7 44%,#57a2ed 100%);background:-ms-linear-gradient(top,#93c9ff 0,#79b8f7 44%,#57a2ed 100%);background:linear-gradient(to bottom,#93c9ff 0,#79b8f7 44%,#57a2ed 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#93c9ff',endColorstr='#57a2ed',GradientType=0)}

2
designs/win98/README.md Normal file
View File

@@ -0,0 +1,2 @@
## Screenshot
![screenshot](https://www.adminer.org/static/designs/win98/screenshot.png)

298
designs/win98/adminer.css Normal file
View File

@@ -0,0 +1,298 @@
/* inspired by https://github.com/jdan/98.css */
:root {
--text-color: #222222;
--button-face: #dfdfdf;
--window-frame: #0a0a0a;
--dialog-blue-light: #1084d0;
--link-blue: #0000ff;
}
body {
background: teal;
color: var(--text-color);
font-family: Arial, Helvetica, sans-serif;
}
button, input[type=reset], input[type=submit] {
background: silver;
border: none;
border-radius: 0;
box-shadow: inset -1px -1px var(--window-frame), inset 1px 1px white, inset -2px -2px grey, inset 2px 2px var(--button-face);
box-sizing: border-box;
color: transparent;
font-size: 12px;
min-height: 23px;
min-width: 75px;
padding: 0 12px;
text-shadow: 0 0 var(--text-color);
}
button.default, input[type=reset].default, input[type=submit].default {
box-shadow: inset -2px -2px var(--window-frame), inset 1px 1px var(--window-frame), inset 2px 2px white, inset -3px -3px grey, inset 3px 3px var(--button-face);
}
button:not(:disabled):active, input[type=reset]:not(:disabled):active, input[type=submit]:not(:disabled):active {
box-shadow: inset -1px -1px white, inset 1px 1px var(--window-frame), inset -2px -2px var(--button-face), inset 2px 2px grey;
text-shadow: 1px 1px var(--text-color);
}
button.default:not(:disabled):active, input[type=reset].default:not(:disabled):active, input[type=submit].default:not(:disabled):active {
box-shadow: inset 2px 2px var(--window-frame), inset -1px -1px var(--window-frame), inset -2px -2px white, inset 3px 3px grey, inset -3px -3px var(--button-face);
}
button:focus, input[type=reset]:focus, input[type=submit]:focus {
outline: 1px dotted black;
outline-offset: -4px;
}
button::-moz-focus-inner, input[type=reset]::-moz-focus-inner, input[type=submit]::-moz-focus-inner {
border: 0;
}
:disabled, :disabled + label, input[readonly], input[readonly] + label, input[type=submit]:disabled, input[type=reset]:disabled, input[type=submit]:disabled {
color: grey;
}
:disabled + label, button:disabled, input[type=reset]:disabled, input[type=submit]:disabled {
text-shadow: 1px 1px 0 white;
}
#content, #menu, .footer, #breadcrumb {
background: silver;
box-shadow: inset -1px -1px var(--window-frame), inset 1px 1px var(--button-face), inset -2px -2px grey, inset 2px 2px white;
padding: 16px;
}
#content h2 {
align-items: center;
background: linear-gradient(90deg, navy, var(--dialog-blue-light));
color: white;
display: flex;
font-size: 120%;
font-weight: normal;
justify-content: space-between;
letter-spacing: 0;
margin: -13px -13px 16px -13px;
padding: 3px 2px 3px 3px;
}
fieldset {
border-image: url("data:image/svg+xml;charset=utf-8,%3Csvg width='5' height='5' fill='gray' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M0 0h5v5H0V2h2v1h1V2H0' fill='%23fff'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M0 0h4v4H0V1h1v2h2V1H0'/%3E%3C/svg%3E") 2;
margin: 0;
padding: 10px;
padding-block-start: 8px;
}
legend {
background: silver;
}
input[type=email], input[type=number], input[type=password], input[type=search], input[type=tel], input[type=text], input:not([type]) {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
border: none;
border-radius: 0;
}
input[type=email], input[type=number], input[type=password], input[type=search], input[type=tel], input[type=text], select, input:not([type]) {
background-color: white;
box-shadow: inset -1px -1px white, inset 1px 1px grey, inset -2px -2px var(--button-face), inset 2px 2px var(--window-frame);
box-sizing: border-box;
padding: 3px 4px;
}
select, textarea {
border: none;
}
textarea {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background-color: white;
border-radius: 0;
box-shadow: inset -1px -1px white, inset 1px 1px grey, inset -2px -2px var(--button-face), inset 2px 2px var(--window-frame);
box-sizing: border-box;
padding: 3px 4px;
}
input[type=email], input[type=password], input[type=search], input[type=tel], input[type=text], input[type=number], select {
min-height: 21px;
}
input[type=search]::-ms-clear, input[type=search]::-ms-reveal {
display: none;
height: 0;
width: 0;
}
input[type=search]::-webkit-search-cancel-button, input[type=search]::-webkit-search-decoration, input[type=search]::-webkit-search-results-button, input[type=search]::-webkit-search-results-decoration {
display: none;
}
input[type=email]:disabled, input[type=email]:read-only, input[type=number]:disabled, input[type=number]:read-only, input[type=password]:disabled, input[type=password]:read-only, input[type=search]:disabled, input[type=search]:read-only, input[type=tel]:disabled, input[type=tel]:read-only, input[type=text]:disabled, input[type=text]:read-only, textarea:disabled {
background-color: silver;
}
select {
appearance: none;
-webkit-appearance: none;
-moz-appearance: none;
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg width='16' height='17' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M15 0H0v16h1V1h14V0z' fill='%23DFDFDF'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M2 1H1v14h1V2h12V1H2z' fill='%23fff'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M16 17H0v-1h15V0h1v17z' fill='%23000'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M15 1h-1v14H1v1h14V1z' fill='gray'/%3E%3Cpath fill='silver' d='M2 2h12v13H2z'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M11 6H4v1h1v1h1v1h1v1h1V9h1V8h1V7h1V6z' fill='%23000'/%3E%3C/svg%3E");
background-position: top 2px right 2px;
background-repeat: no-repeat;
border-radius: 0;
padding-right: 32px;
position: relative;
}
input[type=email]:focus, input[type=number]:focus, input[type=password]:focus, input[type=search]:focus, input[type=tel]:focus, input[type=text]:focus, select:focus, textarea:focus {
outline: none;
}
select:focus {
background-color: navy;
color: white;
}
select:focus option {
background-color: white;
color: black;
}
select:active {
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg width='16' height='17' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M0 0h16v17H0V0zm1 16h14V1H1v15z' fill='gray'/%3E%3Cpath fill='silver' d='M1 1h14v15H1z'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M12 7H5v1h1v1h1v1h1v1h1v-1h1V9h1V8h1V7z' fill='%23000'/%3E%3C/svg%3E")
}
a {
color: var(--link-blue);
}
a:focus {
outline: 1px dotted var(--link-blue);
}
pre {
border-image: url("data:image/svg+xml;charset=utf-8,%3Csvg width='5' height='5' fill='gray' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M0 0h5v5H0V2h2v1h1V2H0' fill='%23fff'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M0 0h4v4H0V1h1v2h2V1H0'/%3E%3C/svg%3E") 2;
border-style: solid;
border-width: 1px;
margin: 0;
padding: 12px 8px;
}
table {
background-color: white;
}
table > thead > tr > * {
background: silver;
box-shadow: inset -1px -1px var(--window-frame), inset 1px 1px white, inset -2px -2px grey, inset 2px 2px var(--button-face);
}
table.interactive > tbody > tr {
cursor: pointer;
}
#fieldset-sort > div:not(:last-of-type), #fieldset-search > div:not(:last-of-type), #fieldset-select > div:not(:last-of-type) {
margin-bottom: .5em;
}
#fieldset-partition p {
margin: 0;
}
.icon {
min-height: 0;
min-width: 0;
}
.links {
margin: 1em 0;
}
#logins a, #tables a, #tables span {
background: none;
}
.jush-autocomplete, .jush-autocomplete:active {
background: none;
height: auto;
}
.logout {
box-shadow: none;
margin-top: .8em;
}
#breadcrumb {
height: auto;
margin: 0;
padding: 2px 16px;
top: calc(1.5em - 14px);
}
code {
background: none;
}
#menu {
margin: 0;
top: 3em;
width: 18em;
}
#menu h1 {
background: none;
margin: 0;
padding: 0;
}
#menu h1 a {
color: var(--text-color);
}
#menu .links a {
display: block;
}
#lang {
top: -2.8em;
}
#lang label {
color: white;
}
#content {
margin-top: 3em;
}
.footer {
margin: 1em 0;
}
.footer > div {
background: none;
}
.js .column {
background: silver;
border: none;
border-radius: 0;
box-shadow: inset -1px -1px var(--window-frame), inset 1px 1px var(--button-face), inset -2px -2px grey, inset 2px 2px white;
margin-top: -.4em;
padding: 4px;
}
@media all and (max-width: 800px) {
.js .logout {
box-shadow: none;
top: 0;
}
#lang label {
color: initial;
}
}

View File

@@ -244,6 +244,18 @@ Adminer generates simple HTML and styles it with basic CSS, respecting user pref
Users can customize styles via `adminer.css`.
If styling an element without a class name is difficult, I generally accept patches that add meaningful class names.
## Translations
All user-visible strings should be translatable using `lang('')`.
This extracts them for translation and applies translations if available.
Translations are updated via [lang.php](/lang.php), which also checks for style consistency, such as matching punctuation.
Plurals are stored as arrays, with selection logic handled in [lang.inc.php](/adminer/include/lang.inc.php).
Plugins extending [`Adminer\Plugin`](/adminer/include/plugin.inc.php) can use `$this->lang()` and store translations in `$translations = array('en' => array('' => 'Plugin description'))`.
The website translations are managed at https://www.adminer.org/en/translations/.
## Compilation
Adminers source code is divided into a manageable number of reasonably small files.
@@ -273,15 +285,6 @@ I do not review logs with this information, and no one else has access to the se
A [plugin](/plugins/version-noverify.php) disables version checks, but users should verify versions by other means to ensure security updates.
There's also a [plugin](/plugins/version-github.php) checking for new versions [from GitHub](https://github.com/vrana/adminer/releases).
## Translations
All user-visible strings should be translatable using `lang('')`.
This extracts them for translation and applies translations if available.
Translations are updated via [lang.php](/lang.php), which also checks for style consistency, such as matching punctuation.
Plurals are stored as arrays, with selection logic handled in [lang.inc.php](/adminer/include/lang.inc.php).
The website translations are managed separately via Google Sheets.
## Commits
Every commit should do only one thing and be as small as possible.

View File

@@ -32,4 +32,5 @@ if (adminer()->homepage()) {
echo "</div>\n";
echo "</form>\n";
echo script("tableCheck();");
adminer()->pluginsLinks();
}

View File

@@ -52,10 +52,16 @@ class Adminer {
return get_databases($flush);
}
function pluginsLinks(): void {
}
function queryTimeout() {
return 5;
}
function afterConnect() {
}
function headers() {
}
@@ -67,12 +73,20 @@ class Adminer {
return true;
}
function bodyClass(): void {
echo " editor";
}
function css() {
$return = array();
foreach (array("", "-dark") as $mode) {
$filename = "adminer$mode.css";
if (file_exists($filename)) {
$return[] = "$filename?v=" . crc32(file_get_contents($filename));
$file = file_get_contents($filename);
$return["$filename?v=" . crc32($file)] = ($mode
? "dark"
: (preg_match('~prefers-color-scheme:\s*dark~', $file) ? '' : 'light')
);
}
}
return $return;
@@ -203,9 +217,9 @@ ORDER BY ORDINAL_POSITION", null, "") as $row
}
function selectVal($val, $link, $field, $original) {
$return = $val;
$return = "$val";
$link = h($link);
if (preg_match('~blob|bytea~', $field["type"]) && !is_utf8($val)) {
if (is_blob($field) && !is_utf8($val)) {
$return = lang('%d byte(s)', strlen($original));
if (preg_match("~^(GIF|\xFF\xD8\xFF|\x89PNG\x0D\x0A\x1A\x0A)~", $original)) { // GIF|JPG|PNG, getimagetype() works with filename
$return = "<img src='$link' alt='$return'>";
@@ -250,13 +264,14 @@ ORDER BY ORDINAL_POSITION", null, "") as $row
$fields = fields($_GET["select"]);
foreach ($columns as $name => $desc) {
$field = $fields[$name];
if (preg_match("~enum~", $field["type"]) || like_bool($field)) { //! set - uses 1 << $i and FIND_IN_SET()
if ($field["type"] == "enum" || like_bool($field)) { //! set - uses 1 << $i and FIND_IN_SET()
$key = $keys[$name];
$i--;
echo "<div>" . h($desc) . input_hidden("where[$i][col]", $name) . ":";
echo "<div>" . h($desc) . ":" . input_hidden("where[$i][col]", $name);
$val = idx($where[$key], "val");
echo (like_bool($field)
? " <select name='where[$i][val]'>" . optionlist(array("" => "", lang('no'), lang('yes')), $where[$key]["val"], true) . "</select>"
: enum_input("checkbox", " name='where[$i][val][]'", $field, (array) $where[$key]["val"], ($field["null"] ? 0 : null))
? "<select name='where[$i][val]'>" . optionlist(array("" => "", lang('no'), lang('yes')), $val, true) . "</select>"
: enum_input("checkbox", " name='where[$i][val][]'", $field, (array) $val, lang('empty'))
);
echo "</div>\n";
unset($columns[$name]);
@@ -354,14 +369,20 @@ ORDER BY ORDINAL_POSITION", null, "") as $row
if ($col != "" || is_numeric($val) || !preg_match(number_type(), $field["type"])) {
$name = idf_escape($name);
if ($col != "" && $field["type"] == "enum") {
$conds[] = (in_array(0, $val) ? "$name IS NULL OR " : "") . "$name IN (" . implode(", ", array_map('intval', $val)) . ")";
$in = array();
foreach ($val as $val1) {
if (preg_match('~val-~', $val1)) {
$in[] = q(substr($val1, 4));
}
}
$conds[] = (in_array("null", $val) ? "$name IS NULL OR " : "") . ($in ? "$name IN (" . implode(", ", $in) . ")" : "0");
} else {
$text_type = preg_match('~char|text|enum|set~', $field["type"]);
$value = adminer()->processInput($field, (!$op && $text_type && preg_match('~^[^%]+$~', $val) ? "%$val%" : $val));
$conds[] = driver()->convertSearch($name, $where, $field) . ($value == "NULL" ? " IS" . ($op == ">=" ? " NOT" : "") . " $value"
: (in_array($op, adminer()->operators()) || $op == "=" ? " $op $value"
: ($text_type ? " LIKE $value"
: " IN (" . str_replace(",", "', '", $value) . ")"
: " IN (" . ($value[0] == "'" ? str_replace(",", "', '", $value) : $value) . ")"
)));
if ($key < 0 && $val == "0") {
$conds[] = "$name IS NULL";
@@ -444,8 +465,8 @@ ORDER BY ORDINAL_POSITION", null, "") as $row
function editInput($table, $field, $attrs, $value) {
if ($field["type"] == "enum") {
return (isset($_GET["select"]) ? "<label><input type='radio'$attrs value='-1' checked><i>" . lang('original') . "</i></label> " : "")
. enum_input("radio", $attrs, $field, ($value || isset($_GET["select"]) ? $value : ""), ($field["null"] ? "" : null))
return (isset($_GET["select"]) ? "<label><input type='radio'$attrs value='orig' checked><i>" . lang('original') . "</i></label> " : "")
. enum_input("radio", $attrs, $field, $value, lang('empty'))
;
}
$options = $this->foreignKeyOptions($table, $field["field"], $value);
@@ -489,7 +510,7 @@ ORDER BY ORDINAL_POSITION", null, "") as $row
if (preg_match('~date|timestamp~', $field["type"]) && preg_match('(^' . str_replace('\$1', '(?P<p1>\d*)', preg_replace('~(\\\\\\$([2-6]))~', '(?P<p\2>\d{1,2})', preg_quote(lang('$1-$3-$5')))) . '(.*))', $value, $match)) {
$return = ($match["p1"] != "" ? $match["p1"] : ($match["p2"] != "" ? ($match["p2"] < 70 ? 20 : 19) . $match["p2"] : gmdate("Y"))) . "-$match[p3]$match[p4]-$match[p5]$match[p6]" . end($match);
}
$return = ($field["type"] == "bit" && preg_match('~^[0-9]+$~', $value) ? $return : q($return));
$return = q($return);
if ($value == "" && like_bool($field)) {
$return = "'0'";
} elseif ($value == "" && ($field["null"] || !preg_match('~char|text~', $field["type"]))) {

View File

@@ -1,6 +1,10 @@
<?php
namespace Adminer;
function doc_link(array $paths, string $text = ""): string {
return "";
}
/** Encode e-mail header in UTF-8 */
function email_header(string $header): string {
// iconv_mime_encode requires iconv, imap_8bit requires IMAP extension
@@ -39,8 +43,8 @@ function send_mail(string $email, string $subject, string $message, string $from
}
/** Check whether the column looks like boolean
* @param Field $field single field returned from fields()
* @param array{type: string} $field single field returned from fields()
*/
function like_bool(array $field): bool {
return preg_match("~bool|(tinyint|bit)\\(1\\)~", $field["full_type"]);
return preg_match("~bool|bit~", $field["type"]);
}

View File

@@ -53,13 +53,3 @@ function whisperClick(event) {
return false;
}
}
/** Add new attachment field
* @this HTMLInputElement
*/
function emailFileChange() {
const el = this.cloneNode(true);
this.onchange = () => { };
el.value = '';
this.parentNode.appendChild(el);
}

2
externals/jush vendored

View File

@@ -12,17 +12,9 @@ if (isset($_SESSION["lang"])) {
}
$messages_all = array();
foreach (
array_merge(
glob(__DIR__ . "/adminer/*.php"),
glob(__DIR__ . "/adminer/include/*.php"),
glob(__DIR__ . "/adminer/drivers/*.php"),
glob(__DIR__ . "/editor/*.php"),
glob(__DIR__ . "/editor/include/*.php")
) as $include
) {
foreach (glob(__DIR__ . "/{adminer,adminer/include,adminer/drivers,editor,editor/include}/*.php", GLOB_BRACE) as $include) {
$file = file_get_contents($include);
if (preg_match_all("~lang\\(('(?:[^\\\\']+|\\\\.)*')([),])~", $file, $matches)) { // lang() always uses apostrophes
if (preg_match_all("~[^>]lang\\(('(?:[^\\\\']+|\\\\.)*')([),])~", $file, $matches)) { // lang() always uses apostrophes
$messages_all += array_combine($matches[1], $matches[2]);
}
}
@@ -33,11 +25,12 @@ foreach (glob(__DIR__ . "/adminer/lang/" . ($_SESSION["lang"] ?: "*") . ".inc.ph
if ($lang != "xx") {
foreach (glob(__DIR__ . "/plugins/*.php") as $filename) {
$file = file_get_contents($filename);
if (preg_match_all("~\\\$this->lang\\(('(?:[^\\\\']+|\\\\.)*')([),])~", $file, $matches)) {
$messages = array_combine($matches[1], $matches[2]);
$file = preg_replace("~(static \\\$translations = array\\((?!.*'$lang').*?)\t\\);~s", "\\1\t\t'$lang' => array(\n\t\t),\n\t);", $file);
if (preg_match('~extends Adminer\\\\Plugin~', $file)) {
preg_match_all("~\\\$this->lang\\(('(?:[^\\\\']+|\\\\.)*')([),])~", $file, $matches);
$messages = array("''" => "") + array_combine($matches[1], $matches[2]);
$file = preg_replace("~(\\\$translations = array\\((?!.*'$lang').*?)\t\\);~s", "\\1\t\t'$lang' => array(\n\t\t),\n\t);", $file);
file_put_contents($filename, $file);
update_translations($lang, $messages, $filename, "~(static \\\$translations = array\\(.*'$lang' => array\\(\n)(.*)(?=^\t\t\\),)~msU", "\t\t\t");
update_translations($lang, $messages, $filename, "~(\\\$translations = array\\(.*'$lang' => array\\(\n)(.*)(?=^\t\t\\),)~msU", "\t\t\t");
}
}
}
@@ -46,12 +39,13 @@ foreach (glob(__DIR__ . "/adminer/lang/" . ($_SESSION["lang"] ?: "*") . ".inc.ph
function update_translations($lang, $messages, $filename, $pattern, $tabs = "\t") {
$file = file_get_contents($filename);
$file = str_replace("\r", "", $file);
$s = preg_replace_callback($pattern, function ($match) use ($lang, $messages, $filename, $file, $tabs) {
$start = 0;
$s = preg_replace_callback($pattern, function ($match) use ($lang, $messages, $filename, $file, $tabs, &$start) {
$prefix = $match[1][0];
$start = $match[2][1];
preg_match_all("~^(\\s*(?:// [^'].*\\s+)?)(?:// )?(('(?:[^\\\\']+|\\\\.)*') => (.*[^,\n])),?~m", $match[2][0], $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE);
$s = "";
$fullstop = ($lang == "bn" ? '।' : (preg_match('~^(ja|zh)~', $lang) ? '。' : ($lang == 'he' ? '[^.]' : '\.')));
$fullstop = ($lang == 'bn' || $lang == 'hi' ? '।' : (preg_match('~^(ja|zh)~', $lang) ? '。' : ($lang == 'he' ? '[^.]' : '\.')));
foreach ($matches as $match) {
list(, list($indent), list($line, $offset), list($en), list($translation)) = $match;
if (isset($messages[$en])) {
@@ -74,6 +68,7 @@ function update_translations($lang, $messages, $filename, $pattern, $tabs = "\t"
}
}
if ($messages) {
$start += strlen($s);
foreach ($messages as $idf => $val) {
// add new messages
if ($val == "," && strpos($idf, "%d")) {
@@ -86,7 +81,8 @@ function update_translations($lang, $messages, $filename, $pattern, $tabs = "\t"
return $prefix . $s;
}, $file, -1, $count, PREG_OFFSET_CAPTURE);
if ($s != $file) {
$s = str_replace("array(\n\t\t\t'' => null,\n\t\t),", "array('' => null),", $s);
file_put_contents($filename, $s);
echo "$filename updated.\n";
echo "$filename:" . (substr_count($s, "\n", 0, $start) + 1) . ":Updated.\n";
}
}

View File

@@ -10,7 +10,7 @@
<exclude-pattern>/(adminer|editor)[-.]</exclude-pattern>
<rule ref="PSR12">
<exclude name="Generic.Whitespace.DisallowTabIndent"/><!-- Replaced by: Generic.Whitespace.DisallowSpaceIndent -->
<exclude name="Generic.WhiteSpace.DisallowTabIndent"/><!-- Replaced by: Generic.WhiteSpace.DisallowSpaceIndent -->
<exclude name="PSR1.Files.SideEffects.FoundWithSymbols"/>
<exclude name="PSR1.Classes.ClassDeclaration.MultipleClasses"/>
<exclude name="PSR2.Classes.ClassDeclaration.OpenBraceNewLine"/><!-- Replaced by: Generic.Classes.OpeningBraceSameLine -->
@@ -47,7 +47,9 @@
<rule ref="PSR1.Methods.CamelCapsMethodName.NotCamelCaps">
<exclude-pattern>adminer/drivers/</exclude-pattern>
<exclude-pattern>adminer/include/db.inc.php</exclude-pattern>
<exclude-pattern>adminer/include/pdo.inc.php</exclude-pattern>
<exclude-pattern>adminer/plugins/foreign-system.php</exclude-pattern>
<exclude-pattern>adminer/plugins/drivers/</exclude-pattern>
</rule>
@@ -56,6 +58,7 @@
<property name="lineLimit" value="250"/>
</properties>
<exclude-pattern>adminer/lang/</exclude-pattern>
<exclude-pattern>plugins/foreign-system.php</exclude-pattern>
</rule>
<rule ref="PSR1.Classes.ClassDeclaration.MissingNamespace">
@@ -84,7 +87,7 @@
<rule ref="Generic.PHP.ForbiddenFunctions"/>
<rule ref="Generic.Strings.UnnecessaryHeredoc"/>
<rule ref="Generic.VersionControl.GitMergeConflict"/>
<rule ref="Generic.Whitespace.DisallowSpaceIndent"/>
<rule ref="Generic.WhiteSpace.DisallowSpaceIndent"/>
<rule ref="Generic.WhiteSpace.LanguageConstructSpacing"/>
<rule ref="Squiz.Arrays.ArrayBracketSpacing"/>
@@ -113,7 +116,6 @@
<rule ref="Squiz.PHP.NonExecutableCode"/>
<rule ref="Squiz.Scope.StaticThisUsage"/>
<rule ref="Squiz.WhiteSpace.FunctionOpeningBraceSpace"/>
<rule ref="Squiz.WhiteSpace.LanguageConstructSpacing"/>
<rule ref="Squiz.WhiteSpace.LogicalOperatorSpacing"/>
<rule ref="Squiz.WhiteSpace.ObjectOperatorSpacing"/>

View File

@@ -10,14 +10,14 @@ parameters:
# not real problems
- identifier: include.fileNotFound # includes in include/ relative from index.php
- identifier: includeOnce.fileNotFound # ./adminer-plugins.php
- "~^Function (set_magic_quotes_runtime|mysql_)~" # PHP < 7 functions
- "~an unknown class OCI-?Lob~" # this looks like PHPStan bug
- "~^Variable \\$error might not be defined~" # declared in bootstrap.inc.php
- "~^Constant LANG not found~" # defined in lang.inc.php
- "~ an undefined \\w+ Adminer\\\\Db::~" # defined in that versions of Db
- "~^Call to an undefined method Adminer\\\\Result::seek~" # defined in MS SQL
- "~^Call to an undefined method Adminer\\\\Driver::setUserTypes~" # defined in PostgreSQL
- "~^Call to an undefined method Adminer\\\\Driver::(setUserTypes|tableOid)~" # defined in PostgreSQL
- "~^Access to an undefined property Adminer\\\\Driver::\\$nsOid~" # defined in PostgreSQL
- "~expects int, float given~" # this will work
- "~expects bool~" # truthy values
- "~fread expects int<1, max>, 100000~" # 1e6
@@ -39,11 +39,13 @@ parameters:
- identifier: booleanAnd.leftAlwaysTrue
- identifier: booleanAnd.rightAlwaysTrue
- identifier: booleanAnd.rightAlwaysFalse
- identifier: booleanOr.rightAlwaysTrue
- identifier: ternary.alwaysTrue
- identifier: if.alwaysTrue
- identifier: while.alwaysTrue
- identifier: isset.offset
- identifier: deadCode.unreachable
- "~Function Adminer\\\\get_driver\\(\\) never returns null~"
- "~on array\\{}~"
paths:
- adminer/drivers/mysql.inc.php # other drivers inherit the annotations so we take them from here
@@ -61,12 +63,13 @@ parameters:
max: 80499
typeAliases:
TableStatus: "array{Name:string, Engine?:?string, Comment?:string, Oid?:numeric-string, Rows?:?numeric-string, Collation?:string, Auto_increment?:?numeric-string, Data_length?:numeric-string, Index_length?:numeric-string, Data_free?:numeric-string, Create_options?:string, nspname?:string}"
Field: "array{field?:string, full_type:string, type:string, length:numeric-string, unsigned:string, default?:string, null:bool, auto_increment:bool, collation:string, privileges:int[], comment:string, primary:bool, generated:string, orig?:string, on_update?:string, on_delete?:string, default_constraint?: string}"
TableStatus: "array{Name:string, Engine?:?string, Comment?:string, Oid?:numeric-string, Rows?:?numeric-string, Collation?:string, Auto_increment?:?numeric-string, Data_length?:numeric-string, Index_length?:numeric-string, Data_free?:numeric-string, Create_options?:string, partition?:numeric-string, nspname?:string}"
Field: "array{field?:string, full_type:string, type:string, length:?numeric-string, unsigned:string, default?:string, null:bool, auto_increment:bool, collation:string, privileges:int[], comment:string, primary:bool, generated:string, orig?:string, on_update?:string, on_delete?:string, default_constraint?: string}"
FieldType: "array{type:string, length:numeric-string, unsigned:string, collation:string}" # subset of RoutineField and Field
RoutineField: "array{field:string, type:string, length:numeric-string, unsigned:string, null:bool, full_type:string, collation:string, inout?:string}"
Index: "array{type:string, columns:list<string>, lengths:list<numeric-string>, descs:list<?bool>}"
Index: "array{type:string, columns:list<string>, lengths:list<numeric-string>, descs:list<?bool>, algorithm?:string, partial?:string}"
ForeignKey: "array{db?:string, ns?:string, table:string, source:list<string>, target:list<?string>, on_delete:string, on_update?:string, definition?:string, deferrable?:string}"
Trigger: "array{Trigger?:string, Timing?:string, Event?:string, Of?:string, Type?:string, Statement?:string}"
Routine: "array{name?:string, fields:list<RoutineField>, comment:string, returns?:FieldType, definition:string, language?:string}"
Partitions: "array{partition_by?:string, partition?:string, partitions?:numeric-string, partition_names?:list<string>, partition_values?:list<string>}"
BackwardKey: "array{name:string, keys:string[][]}"

View File

@@ -8,7 +8,7 @@
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
* @license https://www.gnu.org/licenses/gpl-2.0.html GNU General Public License, version 2 (one or other)
*/
class AdminerDotJs {
class AdminerDotJs extends Adminer\Plugin {
const FILENAME = "adminer.js";
function head($dark = null) {
@@ -16,4 +16,12 @@ class AdminerDotJs {
echo Adminer\script_src(self::FILENAME . "?v=" . crc32(file_get_contents(self::FILENAME))), "\n";
}
}
protected $translations = array(
'cs' => array('' => 'Nahraje adminer.js'),
'de' => array('' => 'Laden Sie adminer.js'),
'pl' => array('' => 'Wczytuj adminer.js'),
'ro' => array('' => 'Încarcă adminer.js'),
'ja' => array('' => 'adminer.js を読込み'),
);
}

View File

@@ -1,26 +1,36 @@
<?php
/** Display links to tables referencing current row, same as in Adminer Editor
* @link https://www.adminer.org/static/plugins/backward-keys.png
* @link https://www.adminer.org/plugins/#use
* @author Jakub Vrana, https://www.vrana.cz/
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
* @license https://www.gnu.org/licenses/gpl-2.0.html GNU General Public License, version 2 (one or other)
*/
class AdminerBackwardKeys {
class AdminerBackwardKeys extends Adminer\Plugin {
// this is copy-pasted from Adminer Editor
function backwardKeys($table, $tableName) {
$return = array();
// we couldn't use the same query in MySQL and PostgreSQL because unique_constraint_name is not table-specific in MySQL and referenced_table_name is not available in PostgreSQL
foreach (
Adminer\get_rows($q = "SELECT TABLE_NAME, CONSTRAINT_NAME, COLUMN_NAME, REFERENCED_COLUMN_NAME
FROM information_schema.KEY_COLUMN_USAGE
WHERE TABLE_SCHEMA = " . Adminer\q(Adminer\DB) . "
AND REFERENCED_TABLE_SCHEMA = " . Adminer\q(Adminer\DB) . "
AND REFERENCED_TABLE_NAME = " . Adminer\q($table) . "
ORDER BY ORDINAL_POSITION", null, "") as $row
Adminer\get_rows("SELECT s.table_name table_name, s.constraint_name constraint_name, s.column_name column_name, " . (Adminer\JUSH == "sql" ? "referenced_column_name" : "t.column_name") . " referenced_column_name
FROM information_schema.key_column_usage s" . (Adminer\JUSH == "sql" ? "
WHERE table_schema = " . Adminer\q(Adminer\DB) . "
AND referenced_table_schema = " . Adminer\q(Adminer\DB) . "
AND referenced_table_name" : "
JOIN information_schema.referential_constraints r USING (constraint_catalog, constraint_schema, constraint_name)
JOIN information_schema.key_column_usage t ON r.unique_constraint_catalog = t.constraint_catalog
AND r.unique_constraint_schema = t.constraint_schema
AND r.unique_constraint_name = t.constraint_name
AND r.constraint_catalog = t.constraint_catalog
AND r.constraint_schema = t.constraint_schema
AND r.unique_constraint_name = t.constraint_name
AND s.position_in_unique_constraint = t.ordinal_position
WHERE t.table_catalog = " . Adminer\q(Adminer\DB) . " AND t.table_schema = " . Adminer\q("$_GET[ns]") . "
AND t.table_name") . " = " . Adminer\q($table) . "
ORDER BY s.ordinal_position", null, "") as $row
) {
$return[$row["TABLE_NAME"]]["keys"][$row["CONSTRAINT_NAME"]][$row["COLUMN_NAME"]] = $row["REFERENCED_COLUMN_NAME"];
$return[$row["table_name"]]["keys"][$row["constraint_name"]][$row["column_name"]] = $row["referenced_column_name"];
}
foreach ($return as $key => $val) {
$name = Adminer\adminer()->tableName(Adminer\table_status1($key, true));
@@ -41,9 +51,12 @@ ORDER BY ORDINAL_POSITION", null, "") as $row
$link = Adminer\ME . 'select=' . urlencode($table);
$i = 0;
foreach ($cols as $column => $val) {
if (!isset($row[$val])) {
continue 2;
}
$link .= Adminer\where_link($i++, $column, $row[$val]);
}
echo "<a href='" . Adminer\h($link) . "'>" . Adminer\h($backwardKey["name"]) . "</a>";
echo "<a href='" . Adminer\h($link) . "'>" . Adminer\h(preg_replace('(^' . preg_quote($_GET["select"]) . (substr($_GET["select"], -1) == 's' ? '?' : '') . '_)', '_', $backwardKey["name"])) . "</a>";
$link = Adminer\ME . 'edit=' . urlencode($table);
foreach ($cols as $column => $val) {
$link .= "&set" . urlencode("[" . Adminer\bracket_escape($column) . "]") . "=" . urlencode($row[$val]);
@@ -52,4 +65,15 @@ ORDER BY ORDINAL_POSITION", null, "") as $row
}
}
}
function screenshot() {
return "https://www.adminer.org/static/plugins/backward-keys.png";
}
protected $translations = array(
'cs' => array('' => 'Zobrazí odkazy na tabulky odkazující aktuální řádek, stejně jako Adminer Editor'),
'de' => array('' => 'Links zu Tabellen anzeigen die auf die aktuelle Zeile verweisen, wie im Adminer Editor'),
'ja' => array('' => 'Adminer Editor と同様に、カレント行を参照しているテーブルへのリンクを表示'),
'pl' => array('' => 'Wyświetlaj linki do tabel odnoszących się do bieżącego wiersza, tak samo jak w Edytorze administratora'),
);
}

View File

@@ -6,7 +6,7 @@
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
* @license https://www.gnu.org/licenses/gpl-2.0.html GNU General Public License, version 2 (one or other)
*/
class AdminerBeforeUnload {
class AdminerBeforeUnload extends Adminer\Plugin {
function head($dark = null) {
?>
@@ -30,4 +30,11 @@ onbeforeunload = () => editChanged;
</script>
<?php
}
protected $translations = array(
'cs' => array('' => 'Zobrazí potvrzení před odnahráním stránky, pokud bylo změněno formulářové políčko'),
'de' => array('' => 'Zeigt eine Bestätigung an bevor die Seite neu geladen wird, wenn ein Formularfeld geändert wurde'),
'ja' => array('' => 'フォームの列が変更された時、ページを再読込みする前に確認を表示'),
'pl' => array('' => 'Wyświetlaj potwierdzenie przed rozładowaniem strony, jeśli pole formularza zostało zmienione'),
);
}

View File

@@ -51,24 +51,18 @@ class AdminerConfig extends Adminer\Plugin {
}
}
function navigation() {
if (Adminer\connection()) { // don't display on login page
$link = substr(preg_replace('~\b(db|ns)=[^&]*&~', '', Adminer\ME), 0, -1);
?>
<style>
#configlink { position: absolute; top: -2.6em; left: 17.8em; }
#configlink a { font-size: 150%; }
@media all and (max-width: 800px) {
#configlink { top: 5em; left: auto; right: 20px; }
}
</style>
<?php
echo "<div id='configlink'><a href='" . Adminer\h($link) . "&config=' title='" . $this->lang('Configuration') . "'>⚙</a></div>\n";
}
function pluginsLinks() {
$link = preg_replace('~\b(db|ns)=[^&]*&~', '', Adminer\ME);
echo "<p><a href='" . Adminer\h($link) . "config='>" . $this->lang('Configuration') . "</a>\n";
}
protected static $translations = array(
function screenshot() {
return "https://www.adminer.org/static/plugins/config.png";
}
protected $translations = array(
'cs' => array(
'' => 'Konfigurace možností uživateli a jejich uložení do cookie',
'Configuration' => 'Konfigurace',
'Configuration saved.' => 'Konfigurace uložena.',
'Only some plugins support configuration, e.g. %s.' => 'Konfiguraci podporují jen některé pluginy, např. %s.',
@@ -84,5 +78,23 @@ class AdminerConfig extends Adminer\Plugin {
'Use %s if exists' => 'Użyj %s, jeśli istnieje',
'Use builtin design' => 'Użyj wbudowanego wyglądu',
),
'de' => array(
'' => 'Optionen durch den Endbenutzer konfigurieren und dies in einem Cookie speichern',
'Configuration' => 'Konfiguration',
'Configuration saved.' => 'Konfiguration gespeichert.',
'Only some plugins support configuration, e.g. %s.' => 'Nur einige Plugins unterstützen die Konfiguration, z.B. %s.',
'Design' => 'Design',
'Use %s if exists' => '%s verwenden, falls vorhanden',
'Use builtin design' => 'Standard Design verwenden',
),
'ja' => array(
'' => 'ユーザオプションを設定し cookie に保存',
'Configuration' => '設定',
'Configuration saved.' => '設定を保存しました。',
'Only some plugins support configuration, e.g. %s.' => '設定変更に対応しているのは一部のプラグインのみです。例: %s。',
'Design' => 'デザイン',
'Use %s if exists' => 'あれば %s を使う',
'Use builtin design' => '組込みのデザインを使う',
),
);
}

View File

@@ -6,7 +6,7 @@
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
* @license https://www.gnu.org/licenses/gpl-2.0.html GNU General Public License, version 2 (one or other)
*/
class AdminerDarkSwitcher {
class AdminerDarkSwitcher extends Adminer\Plugin {
function head($dark = null) {
?>
@@ -28,6 +28,8 @@ const saved = document.cookie.match(/adminer_dark=(\d)/);
if (saved) {
adminerDark = +saved[1];
adminerDarkSet();
} else {
adminerDark = +matchMedia('(prefers-color-scheme: dark)').matches;
}
</script>
<?php
@@ -35,7 +37,18 @@ if (saved) {
function navigation($missing) {
echo "<big style='position: fixed; bottom: .5em; right: .5em; cursor: pointer;'>☀</big>"
. Adminer\script("if (adminerDark != null) adminerDarkSet(); qsl('big').onclick = adminerDarkSwitch;") . "\n"
. Adminer\script("adminerDarkSet(); qsl('big').onclick = adminerDarkSwitch;") . "\n"
;
}
function screenshot() {
return "https://www.adminer.org/static/plugins/dark-switcher.gif";
}
protected $translations = array(
'cs' => array('' => 'Dovoluje přepínání světlého a tmavého vzhledu'),
'de' => array('' => 'Umschalten zwischen hellem und dunklem Design erlauben'),
'ja' => array('' => 'ダークモードへの切替え'),
'pl' => array('' => 'Zezwalaj na przełączanie trybu jasnego i ciemnego'),
);
}

View File

@@ -6,7 +6,7 @@
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
* @license https://www.gnu.org/licenses/gpl-2.0.html GNU General Public License, version 2 (one or other)
*/
class AdminerDatabaseHide {
class AdminerDatabaseHide extends Adminer\Plugin {
protected $disabled;
/**
@@ -25,4 +25,12 @@ class AdminerDatabaseHide {
}
return $return;
}
protected $translations = array(
'cs' => array('' => 'Skryje některé databáze z rozhraní pouze vylepší vzhled, nikoliv bezpečnost'),
'de' => array('' => 'Verstecken Sie einige Datenbanken vor der Benutzeroberfläche nur um das Design zu verbessern, verbessert nicht die Sicherheit'),
'pl' => array('' => 'Ukryj niektóre bazy danych w interfejsie tylko po to, aby ulepszyć motyw, a nie wtyczkę zabezpieczającą'),
'ro' => array('' => 'Ascundeți unele baze de date din interfață - doar pentru a îmbunătăți designul, nu un plugin de securitate'),
'ja' => array('' => '一部データベースを UI 上で表示禁止 (デザイン的な効果のみでセキュリティ的には効果なし)'),
);
}

View File

@@ -6,7 +6,7 @@
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
* @license https://www.gnu.org/licenses/gpl-2.0.html GNU General Public License, version 2 (one or other)
*/
class AdminerDesigns {
class AdminerDesigns extends Adminer\Plugin {
protected $designs;
/**
@@ -16,7 +16,7 @@ class AdminerDesigns {
$this->designs = $designs;
}
function headers() {
function afterConnect() {
if (isset($_POST["design"]) && Adminer\verify_token()) {
Adminer\restart_session();
$_SESSION["design"] = $_POST["design"];
@@ -27,7 +27,7 @@ class AdminerDesigns {
function css() {
$return = array();
if (array_key_exists($_SESSION["design"], $this->designs)) {
$return[] = $_SESSION["design"];
$return[$_SESSION["design"]] = (preg_match('~-dark~', $_SESSION["design"]) ? "dark" : "light");
}
return $return;
}
@@ -38,4 +38,16 @@ class AdminerDesigns {
echo Adminer\input_token();
echo "</form>\n";
}
function screenshot() {
return "https://www.adminer.org/static/plugins/designs.png";
}
protected $translations = array(
'cs' => array('' => 'Umožní změnit vzhled'),
'de' => array('' => 'Designwechsel ermöglichen'),
'pl' => array('' => 'Zezwalaj na przełączanie motywów'),
'ro' => array('' => 'Permiteți comutarea designurilor'),
'ja' => array('' => 'テーマ設定を有効化'),
);
}

View File

@@ -1,3 +1,5 @@
Using drivers: https://www.adminer.org/plugins/#use
Developing drivers: https://www.adminer.org/en/drivers/
The type declarations must be compatible both with source codes and the compiled version (where PHP5-incompatible types are stripped). It means:

View File

@@ -15,14 +15,17 @@ if (isset($_GET["clickhouse"])) {
function rootQuery($db, $query) {
$file = @file_get_contents("$this->url/?database=$db", false, stream_context_create(array('http' => array(
'method' => 'POST',
'content' => $this->isQuerySelectLike($query) ? "$query FORMAT JSONCompact" : $query,
'header' => 'Content-type: application/x-www-form-urlencoded',
'content' => $query,
'header' => array(
'Content-Type: application/x-www-form-urlencoded',
'X-ClickHouse-Format: JSONCompact',
),
'ignore_errors' => 1,
'follow_location' => 0,
'max_redirects' => 0,
))));
if ($file === false || !preg_match('~^HTTP/[0-9.]+ 2~i', $http_response_header[0])) {
if ($file === false || preg_match('~^HTTP/[0-9.]+ 403~i', $http_response_header[0])) {
$this->error = lang('Invalid credentials.');
return false;
}
@@ -45,11 +48,18 @@ if (isset($_GET["clickhouse"])) {
}
}
}
// 400 == Syntax error
// 404 == Unknown expression identifier
// 500 == Column 'x' is not under aggregate function and not in GROUP BY keys
if (preg_match('~^HTTP/[0-9.]+ [45]~i', $http_response_header[0])) {
$this->error = $return['exception'];
return false;
}
return new Result($return);
}
function isQuerySelectLike($query) {
return (bool) preg_match('~^(select|show)~i', $query);
return (bool) preg_match('~^\s*(select|show|with)~i', $query);
}
function query($query, $unbuffered = false) {
@@ -75,7 +85,7 @@ if (isset($_GET["clickhouse"])) {
class Result {
public $num_rows, $columns, $meta;
private $rows, $offset = 0;
private $rows = array(), $offset = 0;
function __construct($result) {
foreach ($result['data'] as $item) {
@@ -243,7 +253,7 @@ if (isset($_GET["clickhouse"])) {
}
function limit($query, $where, $limit, $offset = 0, $separator = " ") {
return " $query$where" . ($limit ? $separator . "LIMIT $limit" . ($offset ? ", $offset" : "") : "");
return " $query$where" . ($limit ? $separator . "LIMIT " . ($offset ? "$offset, " : "") . $limit : "");
}
function limit1($table, $query, $where, $separator = "\n") {
@@ -274,7 +284,7 @@ if (isset($_GET["clickhouse"])) {
function table_status($name = "", $fast = false) {
$return = array();
$tables = get_rows("SELECT name, engine FROM system.tables WHERE database = " . q(connection()->_db));
$tables = get_rows("SELECT name, engine FROM system.tables WHERE database = " . q(connection()->_db) . ($name != "" ? " AND name = " . q($name) : ""));
foreach ($tables as $table) {
$return[$table['name']] = array(
'Name' => $table['name'],

View File

@@ -287,10 +287,6 @@ if (isset($_GET["elastic"])) {
return !!$this->conn->affected_rows;
}
function convertOperator($operator): string {
return $operator == "LIKE %%" ? "should" : $operator;
}
}
function support($feature) {
@@ -412,6 +408,11 @@ if (isset($_GET["elastic"])) {
return $table_status["Engine"] == "view";
}
function view(string $name): array {
$return = connection()->rootQuery("_alias/" . urlencode($name));
return array("select" => implode("\n", array_keys($return)));
}
function error() {
return h(connection()->error);
}
@@ -533,15 +534,18 @@ if (isset($_GET["elastic"])) {
}
}
/** Drop types
* @param list<string> $tables
*/
function drop_views(array $tables): bool {
$return = connection()->rootQuery('_aliases', array('actions' => array_map(function ($table) {
return array('remove' => array('index' => '*', 'alias' => $table));
}, $tables)), 'POST');
return $return && !$return['errors'];
}
function drop_tables(array $tables): bool {
$return = true;
foreach ($tables as $table) { //! convert to bulk api
$return = $return && connection()->rootQuery(urlencode($table), null, 'DELETE');
}
return $return;
}

436
plugins/drivers/igdb.php Normal file
View File

@@ -0,0 +1,436 @@
<?php
/** Driver for https://api-docs.igdb.com/
* @link https://demo.adminer.org/igdb/?igdb=IGDB&db=api
* username: your Client-ID
* password: your access token from https://id.twitch.tv/oauth2/token
* @link https://www.adminer.org/static/plugins/igdb.png
*/
namespace Adminer;
add_driver("igdb", "APICalypse");
if (isset($_GET["igdb"])) {
define('Adminer\DRIVER', "igdb");
class Db extends SqlDb {
public $extension = "json";
public $server_info = "v4";
private $username;
private $password;
function attach($server, $username, $password): string {
$this->username = $username;
$this->password = $password;
return '';
}
function select_db($database) {
return ($database == "api");
}
function request($endpoint, $query, $method = 'POST') {
$context = stream_context_create(array('http' => array(
'method' => $method,
'header' => array(
"Content-Type: text/plain",
"Client-ID: $this->username",
"Authorization: Bearer $this->password",
),
'content' => $query,
'ignore_errors' => true,
)));
$response = file_get_contents("https://api.igdb.com/v4/$endpoint", false, $context);
$return = json_decode($response, true);
if ($http_response_header[0] != 'HTTP/1.1 200 OK') {
if (is_array($return)) {
foreach (is_array($return[0]) ? $return : array($return) as $rows) {
foreach ($rows as $key => $val) {
$this->error .= '<b>' . h($key) . ':</b> ' . (is_url($val) ? '<a href="' . h($val) . '"' . target_blank() . '>' . h($val) . '</a>' : h($val)) . '<br>';
}
}
} else {
$this->error = htmlspecialchars(strip_tags($response), 0, null, false);
}
return false;
}
return $return;
}
function query($query, $unbuffered = false) {
if (preg_match('~^SELECT COUNT\(\*\) FROM (\w+)( WHERE ((MATCH \(search\) AGAINST \((.+)\))|.+))?$~', $query, $match)) {
return new Result(array($this->request("$match[1]/count", ($match[5] ? 'search "' . addcslashes($match[5], '\\"') . '";'
: ($match[3] ? 'where ' . str_replace(' AND ', ' & ', $match[3]) . ';'
: ''
)))));
}
if (preg_match('~^\s*(GET|POST|DELETE)\s+([\w/?=]+)\s*;\s*(.*)$~s', $query, $match)) {
$endpoint = $match[2];
$response = $this->request($endpoint, $match[3], $match[1]);
if ($response === false) {
return $response;
}
$return = new Result(is_array($response[0]) ? $response : array($response));
$return->table = $endpoint;
if ($endpoint == 'multiquery') {
$return->results = $response;
}
return $return;
}
$this->error = "Syntax:<br>POST &lt;endpoint>; fields ...;";
return false;
}
function store_result() {
if ($this->multi && ($result = current($this->multi->results))) {
echo "<h3>" . h($result['name']) . "</h3>\n";
$this->multi->__construct($result['count'] ? array(array('count' => $result['count'])) : $result['result']);
}
return $this->multi;
}
function next_result(): bool {
return $this->multi && next($this->multi->results);
}
function quote($string): string {
return $string;
}
}
class Result {
public $num_rows;
public $table;
public $results = array();
private $result;
private $fields;
function __construct($result) {
$keys = array();
foreach ($result as $i => $row) {
foreach ($row as $key => $val) {
$keys[$key] = null;
if (is_array($val) && is_int($val[0])) {
$result[$i][$key] = "(" . implode(",", $val) . ")";
}
}
}
foreach ($result as $i => $row) {
$result[$i] = array_merge($keys, $row);
}
$this->result = $result;
$this->num_rows = count($result);
$this->fields = array_keys(idx($result, 0, array()));
}
function fetch_assoc() {
$row = current($this->result);
next($this->result);
return $row;
}
function fetch_row() {
$row = $this->fetch_assoc();
return ($row ? array_values($row) : false);
}
function fetch_field(): \stdClass {
$field = current($this->fields);
next($this->fields);
return ($field != '' ? (object) array('name' => $field, 'type' => 15, 'charsetnr' => 0, 'orgtable' => $this->table) : false);
}
}
class Driver extends SqlDriver {
static $extensions = array("json");
static $jush = "igdb";
private static $docsFilename = __DIR__ . DIRECTORY_SEPARATOR . 'igdb-api.html';
public $delimiter = ";;";
public $operators = array("=", "<", ">", "<=", ">=", "!=", "~");
public $tables = array();
public $links = array();
public $fields = array();
public $foreignKeys = array();
public $foundRows = null;
static function connect(string $server, string $username, string $password) {
if (!file_exists(self::$docsFilename)) {
return "Download https://api-docs.igdb.com/ and save it as " . self::$docsFilename; // copy() doesn't work - bot protection
}
return parent::connect($server, $username, $password);
}
function __construct($connection) {
parent::__construct($connection);
libxml_use_internal_errors(true);
$dom = new \DOMDocument();
$dom->loadHTMLFile(self::$docsFilename);
$xpath = new \DOMXPath($dom);
$els = $xpath->query('//div[@class="content"]/*');
$link = '';
foreach ($els as $i => $el) {
if ($el->tagName == 'h2') {
$link = $el->getAttribute('id');
}
if ($el->nodeValue == 'Request Path') {
$table = preg_replace('~^https://api.igdb.com/v4/~', '', $els[$i+1]->firstElementChild->nodeValue);
$comment = $els[$i-1]->tagName == 'p' ? $els[$i-1]->nodeValue : '';
if (preg_match('~^DEPRECATED!~', $comment)) {
continue;
}
$this->fields[$table]['id'] = array('full_type' => 'bigint', 'comment' => '');
$this->links[$link] = $table;
$this->tables[$table] = array('Name' => $table, 'Comment' => $comment);
foreach ($xpath->query('tbody/tr', $els[$i+2]) as $tr) {
$tds = $xpath->query('td', $tr);
$field = $tds[0]->nodeValue;
$comment = $tds[2]->nodeValue;
if ($field != 'checksum' && $field != 'content_descriptions' && !preg_match('~^DEPRECATED!~', $comment)) {
$this->fields[$table][$field] = array(
'full_type' => str_replace(' ', ' ', $tds[1]->nodeValue),
'comment' => str_replace(' ', ' ', $comment),
);
$ref = $xpath->query('a/@href', $tds[1]);
if (count($ref) && !in_array($ref[0]->value, array('#game-version-feature-enums', '#tag-numbers'))) {
$this->foreignKeys[$table][$field] = substr($ref[0]->value, 1);
} elseif ($field === 'game_id') { // game_time_to_beats, popularity_primitives
$this->foreignKeys[$table][$field] = 'game';
}
}
}
uksort($this->fields[$table], function ($a, $b) use ($table) {
return (($b == 'id') - ($a == 'id'))
?: (($b == 'name') - ($a == 'name'))
?: (($a == 'updated_at') - ($b == 'updated_at'))
?: (($a == 'created_at') - ($b == 'created_at'))
?: (!idx($this->foreignKeys[$table], $b) - !idx($this->foreignKeys[$table], $a))
?: ($a < $b ? -1 : 1)
;
});
}
}
$this->tables['webhooks'] = array('Name' => 'webhooks', 'Comment' => 'Webhooks allow us to push data to you when it is added, updated, or deleted');
$this->links['webhooks'] = 'webhooks';
$this->fields['webhooks'] = array(
'endpoint' => array(
'full_type' => 'String',
'comment' => 'Specify what type of data you want from your webhook',
'privileges' => array('insert' => 1),
),
'id' => array('comment' => 'A unique ID for the webhook'),
'url' => array(
'full_type' => 'String',
'length' => '100',
'comment' => 'Your prepared url that is ready to accept data from us',
'privileges' => array('select' => 1, 'insert' => 1),
),
'method' => array(
'full_type' => 'enum',
'length' => "('create','delete','update')",
'comment' => 'The type of data you are expecting to your url, there are three types of methods',
'privileges' => array('insert' => 1),
),
'category' => array('comment' => 'Based on the endpoint you chose'),
'sub_category' => array('comment' => 'Based on your method (can be 0, 1, 2)'),
'active' => array('comment' => 'Is the webhook currently active'),
'api_key' => array('comment' => 'Displays the api key the webhook is connected to'),
'secret' => array(
'full_type' => 'String',
'comment' => 'Your “secret” password for your webhook',
'privileges' => array('select' => 1, 'insert' => 1),
),
'created_at' => array('comment' => 'Created at date'),
'updated_at' => array('comment' => 'Updated at date'),
);
}
function select($table, $select, $where, $group, $order = array(), $limit = 1, $page = 0, $print = false) {
$query = '';
$search = preg_match('~^MATCH \(search\) AGAINST \((.+)\)$~', $where[0], $match);
if ($search) {
$query = 'search "' . addcslashes($match[1], '\\"') . "\";\n";
unset($where[0]);
}
foreach ($where as $i => $val) {
$where[$i] = str_replace(' OR ', ' | ', $val);
}
$columns = ($select != array('*') ? $select : array_keys($this->fields[$table]));
$common = ($where ? "\nwhere " . implode(" & ", $where) . ";" : "");
if ($table != 'webhooks') {
$query .= "fields " . implode(",", $select) . ";"
. ($select == array('*') ? "\nexclude checksum;" : "")
. $common
. ($order ? "\nsort " . strtolower(implode(",", $order)) . ";" : "")
. "\nlimit $limit;"
. ($page ? "\noffset " . ($page * $limit) . ";" : "")
;
}
$start = microtime(true);
$multi = (!$search && $table != 'webhooks' && array_key_exists($table, driver()->tables));
$method = ($table == 'webhooks' ? 'GET' : 'POST');
$realQuery = str_replace("*;\nexclude checksum", implode(',', $columns), $query); // exclude deprecated columns
$return = ($multi
? $this->conn->request('multiquery', "query $table \"result\" { $realQuery };\nquery $table/count \"count\" { $common };")
: $this->conn->request($table, $realQuery, $method)
);
if ($print) {
echo adminer()->selectQuery("$method $table;\n$query", $start);
}
if ($return === false) {
return $return;
}
$this->foundRows = ($multi ? $return[1]['count'] : null);
$return = ($multi ? $return[0]['result'] : $return);
if ($return && $table != 'webhooks') {
$return[0] = array_merge(array_fill_keys($columns, null), $return[0]);
}
return new Result($return);
}
function insert($table, $set) {
$content = array();
foreach ($set as $key => $val) {
if ($key != 'endpoint') {
$content[] = urlencode($key) . '=' . urlencode($val);
}
}
return queries("POST $set[endpoint]/$table; " . implode('&', $content));
}
function delete($table, $queryWhere, $limit = 0) {
preg_match_all('~\bid = (\d+)~', $queryWhere, $matches);
$this->conn->affected_rows = 0;
foreach ($matches[1] as $id) {
$result = queries("DELETE $table/$id;");
if (!$result) {
return false;
}
$row = $result->fetch_row();
if (!$row[0]) {
$this->conn->error = "ID $id not found.";
return false;
}
$this->conn->affected_rows++;
}
return true;
}
function value($val, $field): ?string {
return ($val && in_array($field['full_type'], array('Unix Time Stamp', 'datetime')) ? str_replace(' 00:00:00', '', gmdate('Y-m-d H:i:s', $val)) : $val);
}
function tableHelp($name, $is_view = false) {
return strtolower("https://api-docs.igdb.com/#" . array_search($name, $this->links));
}
}
function logged_user() {
return $_GET["username"];
}
function get_databases($flush) {
return array("api");
}
function collations() {
return array();
}
function db_collation($db, $collations) {
}
function information_schema($db) {
}
function indexes($table, $connection2 = null) {
$return = array(array("type" => "PRIMARY", "columns" => array("id")));
if (in_array($table, array('characters', 'collections', 'games', 'platforms', 'themes'))) { // https://api-docs.igdb.com/#search-1
$return[] = array("type" => "FULLTEXT", "columns" => array("search"));
}
return $return;
}
function fields($table) {
$return = array();
foreach (driver()->fields[$table] ?: array() as $key => $val) {
$type = strtolower(preg_replace('~ .*~', '', $val['full_type']));
$return[$key] = $val + array(
"field" => $key,
"type" => ($type == 'reference' ? 'int' : $type), // align right reference columns
"privileges" => array("select" => 1) + ($table == 'webhooks' ? array() : array("where" => 1, "order" => 1)),
);
}
return $return;
}
function convert_field($field) {
}
function unconvert_field($field, $return) {
return $return;
}
function limit($query, $where, $limit, $offset = 0, $separator = " ") {
return $query;
}
function idf_escape($idf) {
return $idf;
}
function table($idf) {
return idf_escape($idf);
}
function foreign_keys($table) {
$return = array();
foreach (driver()->foreignKeys[$table] ?: array() as $key => $val) {
$return[] = array(
'table' => driver()->links[$val],
'source' => array($key),
'target' => array('id'),
);
}
return $return;
}
function tables_list() {
return array_fill_keys(array_keys(table_status()), 'table');
}
function table_status($name = "", $fast = false) {
$tables = driver()->tables;
return ($name != '' ? ($tables[$name] ? array($name => $tables[$name]) : array()) : $tables);
}
function count_tables($databases) {
return array(reset($databases) => count(tables_list()));
}
function error() {
return connection()->error;
}
function is_view($table_status) {
return false;
}
function found_rows($table_status, $where) {
return driver()->foundRows;
}
function fk_support($table_status) {
return true;
}
function last_id($result): string {
$row = $result->fetch_assoc();
return (string) $row['id'];
}
function support($feature) {
return in_array($feature, array('columns', 'comment', 'sql', 'table'));
}
}

View File

@@ -25,7 +25,8 @@ if (isset($_GET["imap"])) {
private $imap;
function attach($server, $username, $password): string {
$this->mailbox = "{" . "$server:993/ssl}"; // Adminer disallows specifying privileged port in server name
list($host, $port) = host_port($server);
$this->mailbox = "{" . "$host:" . ($port ?: 993) . "/ssl}"; // Adminer disallows specifying privileged port in server name
$this->imap = @imap_open($this->mailbox, $username, $password, OP_HALFOPEN, 1);
return ($this->imap ? '' : imap_last_error());
}
@@ -117,7 +118,7 @@ if (isset($_GET["imap"])) {
function __construct($result) {
$this->result = $result;
$this->num_rows = count($result);
$this->fields = array_keys(idx($result, 0, array()));
$this->fields = array_keys(idx($result, 0, array()));
}
function fetch_assoc() {

View File

@@ -6,7 +6,7 @@
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
* @license https://www.gnu.org/licenses/gpl-2.0.html GNU General Public License, version 2 (one or other)
*/
class AdminerDumpAlter {
class AdminerDumpAlter extends Adminer\Plugin {
function dumpFormat() {
if (Adminer\DRIVER == 'server') {
@@ -167,4 +167,12 @@ DROP PROCEDURE adminer_alter;
$this->dumpAlter();
}
}
protected $translations = array(
'cs' => array('' => 'Exportuje jednu databázi (např. vývojovou) tak, že může být synchronizována s jinou databází (např. produkční)'),
'de' => array('' => 'Exportiert eine Datenbank (z. B. Entwicklung), damit sie mit einer anderen Datenbank (z. B. Produktion) synchronisiert werden kann'),
'pl' => array('' => 'Eksportuje jedną bazę danych (np. programistyczną), aby można ją było zsynchronizować z inną bazą danych (np. produkcyjną)'),
'ro' => array('' => 'Exportați o bază de date (de exemplu, development) astfel încât să poată fi sincronizată cu o altă bază de date (de exemplu, de producție)'),
'ja' => array('' => 'データベース (開発用など) をエクスポートし、別のデータベース (本番用など) と同期'),
);
}

View File

@@ -7,7 +7,7 @@
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
* @license https://www.gnu.org/licenses/gpl-2.0.html GNU General Public License, version 2 (one or other)
*/
class AdminerDumpBz2 {
class AdminerDumpBz2 extends Adminer\Plugin {
protected $filename, $fp;
function dumpOutput() {
@@ -36,4 +36,12 @@ class AdminerDumpBz2 {
ob_start(array($this, '_bz2'), 1e6);
}
}
protected $translations = array(
'cs' => array('' => 'Export do formátu Bzip2'),
'de' => array('' => 'Export im Bzip2-Format'),
'pl' => array('' => 'Zrzuć do formatu Bzip2'),
'ro' => array('' => 'Dump în format Bzip2'),
'ja' => array('' => 'Bzip2 形式でエクスポート'),
);
}

View File

@@ -6,9 +6,17 @@
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
* @license https://www.gnu.org/licenses/gpl-2.0.html GNU General Public License, version 2 (one or other)
*/
class AdminerDumpDate {
class AdminerDumpDate extends Adminer\Plugin {
function dumpFilename($identifier) {
return Adminer\friendly_url(($identifier != "" ? $identifier : (Adminer\SERVER != "" ? Adminer\SERVER : "localhost")) . "-" . Adminer\get_val("SELECT NOW()"));
return Adminer\friendly_url(($identifier != "" ? $identifier : (Adminer\SERVER ?: "localhost")) . "-" . Adminer\get_val("SELECT NOW()"));
}
protected $translations = array(
'cs' => array('' => 'Do názvu souboru s exportem přidá aktuální datum a čas'),
'de' => array('' => 'Aktuelles Datum und die aktuelle Uhrzeit in den Namen der Exportdatei einfügen'),
'pl' => array('' => 'Dołącz bieżącą datę i godzinę do nazwy pliku eksportu'),
'ro' => array('' => 'Includeți data și ora curentă în numele fișierului de export'),
'ja' => array('' => 'エクスポートファイル名に現在日時を含める'),
);
}

View File

@@ -6,7 +6,7 @@
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
* @license https://www.gnu.org/licenses/gpl-2.0.html GNU General Public License, version 2 (one or other)
*/
class AdminerDumpJson {
class AdminerDumpJson extends Adminer\Plugin {
protected $database = false;
function dumpFormat() {
@@ -57,4 +57,12 @@ class AdminerDumpJson {
echo "}\n";
}
}
protected $translations = array(
'cs' => array('' => 'Export do formátu JSON'),
'de' => array('' => 'Export im JSON-Format'),
'pl' => array('' => 'Zrzuć do formatu JSON'),
'ro' => array('' => 'Dump în format JSON'),
'ja' => array('' => 'JSON 形式でエクスポート'),
);
}

View File

@@ -5,7 +5,7 @@
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
* @license https://www.gnu.org/licenses/gpl-2.0.html GNU General Public License, version 2 (one or other)
*/
class AdminerDumpPhp {
class AdminerDumpPhp extends Adminer\Plugin {
protected $output = array();
function dumpFormat() {
@@ -45,4 +45,12 @@ class AdminerDumpPhp {
echo ";\n";
}
}
protected $translations = array(
'cs' => array('' => 'Export do formátu PHP'),
'de' => array('' => 'Export im PHP-Format'),
'pl' => array('' => 'Zrzucaj do formatu PHP'),
'ro' => array('' => 'Dump în format PHP'),
'ja' => array('' => 'PHP 形式でエクスポート'),
);
}

View File

@@ -6,7 +6,7 @@
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
* @license https://www.gnu.org/licenses/gpl-2.0.html GNU General Public License, version 2 (one or other)
*/
class AdminerDumpXml {
class AdminerDumpXml extends Adminer\Plugin {
protected $database = false;
function dumpFormat() {
@@ -51,4 +51,12 @@ class AdminerDumpXml {
echo "</database>\n";
}
}
protected $translations = array(
'cs' => array('' => 'Export do formátu XML ve struktuře <database name=""><table name=""><column name="">value'),
'de' => array('' => 'Export im XML-Format in der Struktur <database name="><table name=""><column name="">value'),
'pl' => array('' => 'Zrzut do formatu XML w strukturze <database name=""><table name=""><column name="">value'),
'ro' => array('' => 'Dump în format XML în structura <database name=""><table name=""><column name="">value'),
'ja' => array('' => '構造化 XML 形式でエクスポート <database name=""><table name=""><column name="">value'),
);
}

View File

@@ -7,7 +7,7 @@
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
* @license https://www.gnu.org/licenses/gpl-2.0.html GNU General Public License, version 2 (one or other)
*/
class AdminerDumpZip {
class AdminerDumpZip extends Adminer\Plugin {
protected $filename, $data;
function dumpOutput() {
@@ -40,4 +40,12 @@ class AdminerDumpZip {
ob_start(array($this, '_zip'));
}
}
protected $translations = array(
'cs' => array('' => 'Export do formátu ZIP'),
'de' => array('' => 'Export Im ZIP-Format'),
'pl' => array('' => 'Zrzuć do formatu ZIP'),
'ro' => array('' => 'Dump în format ZIP'),
'ja' => array('' => 'ZIP 形式でエクスポート'),
);
}

View File

@@ -8,7 +8,7 @@
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
* @license https://www.gnu.org/licenses/gpl-2.0.html GNU General Public License, version 2 (one or other)
*/
class AdminerEditCalendar {
class AdminerEditCalendar extends Adminer\Plugin {
protected $prepend, $langPath;
/**
@@ -50,4 +50,12 @@ class AdminerEditCalendar {
);
}
}
protected $translations = array(
'cs' => array('' => 'Zobrazí jQuery UI Timepicker pro každé datumové a časové políčko'),
'de' => array('' => 'Zeigen Sie die jQuery-UI Timepicker für jedes Datums- und Datum/Uhrzeit-Feld an'),
'pl' => array('' => 'Wyświetl interfejs jQuery Timepicker dla każdego pola daty i godziny'),
'ro' => array('' => 'Afișați jQuery UI Timepicker pentru fiecare câmp de dată și dată-timp'),
'ja' => array('' => '各日時列に jQuery UI の Timepicker を表示'),
);
}

View File

@@ -6,7 +6,7 @@
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
* @license https://www.gnu.org/licenses/gpl-2.0.html GNU General Public License, version 2 (one or other)
*/
class AdminerEditForeign {
class AdminerEditForeign extends Adminer\Plugin {
protected $limit;
function __construct($limit = 0) {
@@ -39,4 +39,12 @@ class AdminerEditForeign {
}
}
}
protected $translations = array(
'cs' => array('' => 'Výběr cizího klíče v editačním formuláři'),
'de' => array('' => 'Wählen Sie im Bearbeitungsformular den Fremdschlüssel aus'),
'pl' => array('' => 'Wybierz klucz obcy w formularzu edycji'),
'ro' => array('' => 'Selectați cheia străină în formularul de editare'),
'ja' => array('' => '外部キーを編集フォームで選択'),
);
}

View File

@@ -6,11 +6,19 @@
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
* @license https://www.gnu.org/licenses/gpl-2.0.html GNU General Public License, version 2 (one or other)
*/
class AdminerEditTextarea {
class AdminerEditTextarea extends Adminer\Plugin {
function editInput($table, $field, $attrs, $value) {
if (preg_match('~char~', $field["type"])) {
return "<textarea cols='30' rows='1'$attrs>" . Adminer\h($value) . '</textarea>';
}
}
protected $translations = array(
'cs' => array('' => 'Použije <textarea> pro char a varchar'),
'de' => array('' => 'Verwenden Sie <textarea> für char und varchar Felder'),
'pl' => array('' => 'Użyj <textarea> dla char i varchar'),
'ro' => array('' => 'Utilizați <textarea> pentru char și varchar'),
'ja' => array('' => 'char や varchar に <textarea> を使用'),
);
}

View File

@@ -6,7 +6,7 @@
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
* @license https://www.gnu.org/licenses/gpl-2.0.html GNU General Public License, version 2 (one or other)
*/
class AdminerEditorSetup {
class AdminerEditorSetup extends Adminer\Plugin {
private $driver;
private $server;
private $database;
@@ -37,4 +37,11 @@ class AdminerEditorSetup {
return $this->database;
}
}
protected $translations = array(
'cs' => array('' => 'Nastavit ovladač, server a databázi pro použití s Adminer Editorem'),
'de' => array('' => 'Treiber, Server und Datenbank für die Verwendung mit Adminer Editor einrichten'),
'ja' => array('' => 'Adminer Editor で使用するドライバ、サーバ、データベースを設定'),
'pl' => array('' => 'Konfiguruj sterownik, serwer i bazę danych do użycia z Adminer Editorem'),
);
}

View File

@@ -6,9 +6,17 @@
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
* @license https://www.gnu.org/licenses/gpl-2.0.html GNU General Public License, version 2 (one or other)
*/
class AdminerEditorViews {
class AdminerEditorViews extends Adminer\Plugin {
function tableName($tableStatus) {
return Adminer\h($tableStatus["Comment"] != "" ? $tableStatus["Comment"] : $tableStatus["Name"]);
}
protected $translations = array(
'cs' => array('' => 'Zobrazení pohledů v Adminer Editoru'),
'de' => array('' => 'Views im Adminer Editor anzeigen'),
'pl' => array('' => 'Wyświetlaj widoki w Adminer Editorze'),
'ro' => array('' => 'Afișează vizualizări în Adminer Editor'),
'ja' => array('' => 'Adminer Editor にビューを表示'),
);
}

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